Introduction.

This notebook reports on the inference of microbial association networks from meta-taxonomic data extracted from DairyFMBN using package NetCoMi, after some pre-processing carried out mostly with package phyloseq.

The objective of this analysis is:

  • infer microbial association networks (at the genus level) for selected cheese studies stored in DairyFMBN usign different inference methods

  • identify associations which are conserved across methods

  • compare different inference methods

  • identify and compare hubs in different networks

The data used in this script include studies from FMBN versions 3.2, with different platforms and target, but analyzed using the same pipeline based on DADA2. The script is designed to work with phyloseq objects extracted from FoodMicrobionet or DairyFMBN using the ShinyFMBN app DairyFMBN can be downloaded from Mendeley data: new versions are due soon.
The script will also work correctly with phyloseq objects generated using the DADA2 BioConductor pipeline but the use of SILVA as a taxonomic database is advisable. With some adaptations the script may work with phyloseq objects generated using other pipelines.

Analysis workflow.

  1. start from n (n>1) phyloseq objects. In this analysis I will use phyloseq objects extracted from DairyFMBN (which, in turn, includes all studies on dairy products stored in FoodMicrobionet). Although pooling at the genus level is likely to muddle some interactions, it it the only reasonable choice when comparing studies with vastly different targets, sequencing platforms, bioinformatic pilelines, etc.

  2. perform data-wrangling with phyloseq functions (mostly). There are a number of basic operations which should be performed:

    • remove samples with a low number of sequences (minseq) using the prune_samples() functions (and perhaps skip the analysis if there is less than min_samples). Note that sample and taxonomic filtering can be also performed within the NetCoMi::netConstruct() function

    • remove Eukaryota and Chloroplasts using subset_taxa()

    • remove taxa with ambiguous identification or poor identification (say at the domain or phylum level)

    • perform prevalence and abundance filtering (prevalence above min_prev AND abundance above min_rel_ab), possibly using filter_taxa()

    • return some sort of summary for what has been done and the effect on the original table (in terms of loss of taxa, sequences, samples), including some measure of diversity and evenness

  3. perform microbial association network inference with netConstruct() with some error trapping and store results in a list. This example uses the following inference methods:

    • SparCC (sparcc() in SpiecEasi package): needs high number of taxa and sparse matrix

    • CCREPE (ccrepe package, it is the main approach used in CoNet)

    • SpiecEasi (SpiecEasi package)

    • SPRING (SPRING package)

  4. get nodes, edges and network stats: use NetCoMi::netAnalyze() to get an object with both node and network stats; print a summary for network stats; build a tidygraph object for further stats; build tidy data frames with relevant stats;

The script is esigned to save data and amny of the plots in a subfolder (here “netcompare_output”).

The analysis.

Setting the options.

A number of options pertaining to the analysis or rlated to save operations can be set in the chunk below and will be saved and printed.

$general_options
$general_options$ncores
[1] 2

$general_options$data_folder
[1] "input_data"

$general_options$source_folder
[1] "source"

$general_options$output_folder
[1] "netcompare_output"

$general_options$process_batch
[1] TRUE

$general_options$use_lookup
[1] TRUE

$general_options$out_filenames
[1] "MAN_FMBN_genus_3"

$general_options$gres
[1] 300

$general_options$gtype
[1] "tiff"


$methods_options
$methods_options$methods
[1] "spieceasi" "spring"    "sparcc"    "ccrepe"   

$methods_options$parameters
$methods_options$parameters$spieaceasi
$methods_options$parameters$spieaceasi$sparMethod
[1] "t-test"

$methods_options$parameters$spieaceasi$alpha
[1] 0.001

$methods_options$parameters$spieaceasi$measureParList
$methods_options$parameters$spieaceasi$measureParList$method
[1] "mb"

$methods_options$parameters$spieaceasi$measureParList$lambda.min.ratio
[1] 0.01

$methods_options$parameters$spieaceasi$measureParList$nlambda
[1] 20

$methods_options$parameters$spieaceasi$measureParList$pulsar.params
$methods_options$parameters$spieaceasi$measureParList$pulsar.params$rep.num
[1] 50



$methods_options$parameters$spieaceasi$normmethodPar
[1] "none"

$methods_options$parameters$spieaceasi$zeromethodPar
[1] "none"

$methods_options$parameters$spieaceasi$dissFuncPar
[1] "signed"

$methods_options$parameters$spieaceasi$verbosePar
[1] 1


$methods_options$parameters$spring
$methods_options$parameters$spring$sparMethod
[1] "t-test"

$methods_options$parameters$spring$alpha
[1] 0.001

$methods_options$parameters$spring$measureParList
$methods_options$parameters$spring$measureParList$nlambda
[1] 50

$methods_options$parameters$spring$measureParList$rep.num
[1] 50


$methods_options$parameters$spring$normmethodPar
[1] "none"

$methods_options$parameters$spring$zeromethodPar
[1] "none"

$methods_options$parameters$spring$dissFuncPar
[1] "signed"

$methods_options$parameters$spring$verbosePar
[1] 1


$methods_options$parameters$sparcc
$methods_options$parameters$sparcc$sparMethod
[1] "t-test"

$methods_options$parameters$sparcc$alpha
[1] 0.001

$methods_options$parameters$sparcc$measureParList
$methods_options$parameters$sparcc$measureParList$iter
[1] 100

$methods_options$parameters$sparcc$measureParList$inner_it
[1] 20

$methods_options$parameters$sparcc$measureParList$th
[1] 0.05


$methods_options$parameters$sparcc$normmethodPar
[1] "none"

$methods_options$parameters$sparcc$zeromethodPar
[1] "none"

$methods_options$parameters$sparcc$dissFuncPar
[1] "signed"

$methods_options$parameters$sparcc$verbosePar
[1] 1


$methods_options$parameters$ccrepe
$methods_options$parameters$ccrepe$sparMethod
[1] "t-test"

$methods_options$parameters$ccrepe$alpha
[1] 0.001

$methods_options$parameters$ccrepe$measureParList
NULL

$methods_options$parameters$ccrepe$normmethodPar
[1] "fractions"

$methods_options$parameters$ccrepe$zeromethodPar
[1] "none"

$methods_options$parameters$ccrepe$dissFuncPar
[1] "signed"

$methods_options$parameters$ccrepe$verbosePar
[1] 1




$filtering_options
$filtering_options$min_samples
[1] 20

$filtering_options$min_seqs
[1] 1000

$filtering_options$glom_taxa
[1] "genus"

$filtering_options$rmunchar_dp
[1] TRUE

$filtering_options$rmchlmit
[1] TRUE

$filtering_options$rmeuk
[1] TRUE

$filtering_options$rmunchar_cof
[1] TRUE

$filtering_options$filter_OTUs
[1] TRUE

$filtering_options$prevfilter
[1] TRUE

$filtering_options$prevthreshold
[1] 0.05

$filtering_options$abthreshols
[1] 0.005

$filtering_options$passboth
[1] TRUE

$filtering_options$saveprevabplot
[1] TRUE

$filtering_options$printprevabplot
[1] FALSE

$filtering_options$saveprevabtable
[1] TRUE

$filtering_options$save_prev_ab_list
[1] TRUE


$netstat_options
$netstat_options$doVenn
[1] TRUE

$netstat_options$calcEbetw
[1] TRUE

$netstat_options$mergeNstats
[1] TRUE

Loading the objects to process.

The input data are stored in a folder (input_data) and processed one by one (depending on user input) or in batch (see process_batch option). The folder should also contain a .tsv with study metadata, with a label variable with the number of the study and an indicator on the nature of the file (FMBN or, if the accn number is in the name, ASV). Here I am loading 5 studies from DairyFMBN (ST106, ST110, ST115, ST131, ST136). The studies differ in the targets (ST106 targets V1-V3 RNA, the others the 16S RNA gene, V4 or V3-V4) and platform. Look at the example files to see how the study_metadata file should be setup.


── Column specification ──────────────────────────────────────────────
cols(
  .default = col_character(),
  read_length_bp = col_double(),
  samples = col_double()
)
ℹ Use `spec()` for the full column specifications.
loading phyloseq objects: 1.737 sec elapsed

Filtering.

Filtering optionally filters samples and taxa (on the basis of taxonomy and of a prevalence and abundance filter) and returns the filtered phyloseq objects in a separate list. Taxonomic agglomeration is optionally carried out. The options for filtering are those defined in the options section.

Filter samples.

Samples with less than a given number of sequences are discarded first. As a consequence, a given phyloseq object may fall out below the set minimum of samples. Therefore the number of samples per object is checked again.

if(keep_time) tic("filter and glom samples")
# first prune objects with less than min_seqs sequences
physeq_list_0  <- physeq_list
# create first step of the filtering report
filtering_report <- vector("list", length = length(physeq_list))
filtering_report_0 <- map(physeq_list, report_step_0)
for(i in seq_along(physeq_list)){
  cat("processing ", i, " of ", length(physeq_list), "\n")
  # set plot_ecdf=F for faster execution
  physeq_list_0[[i]] <- prune_samples_by_size(physeq_list[[i]],
                                              names(physeq_list)[i], 
                                              plot_ecdf = F, 
                                              minseqs = min_seqs)
}
processing  1  of  5 
processing  2  of  5 
processing  3  of  5 
processing  4  of  5 
processing  5  of  5 
cat("...done pruning samples...\n")
...done pruning samples...
# now recheck if all objects have enough samples
physeq_list_1 <- rem_low_sample_obj(physeq_list_0, ms = min_samples)
# create second step of the filtering report
stage_name = "prune samples"
for(i in seq_along(physeq_list_0)){
  if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
    name <- names(physeq_list_0)[i]
    stage <- report_step_n(my_physeq = physeq_list_1[[name]], 
                           my_physeq_o = physeq_list_0[[i]], 
                           stage_name = stage_name) 
  } else {
    stage <- c(stage_name,
              samples = NA_real_,
              sequences = NA_real_,
              taxa = NA_real_,
              prop_samples = NA_real_,
              prop_seq = NA_real_,
              prop_taxa = NA_real_)
  }
  filtering_report[[i]] <- rbind(stage_0 = as.character(filtering_report_0[[i]]), stage_1 =stage)
  names(filtering_report)[i] <- names(physeq_list_0)[i]
}


if(verbose_output) print(filtering_report)
$ST106_FMBN_ps
        stage           samples sequences taxa prop_samples prop_seq
stage_0 "original"      "30"    "128735"  "56" "1"          "1"     
stage_1 "prune samples" "30"    "128735"  "56" "1"          "1"     
        prop_taxa
stage_0 "1"      
stage_1 "1"      

$ST110_FMBN_ps
        stage           samples sequences taxa prop_samples prop_seq
stage_0 "original"      "112"   "2056840" "90" "1"          "1"     
stage_1 "prune samples" "112"   "2056840" "90" "1"          "1"     
        prop_taxa
stage_0 "1"      
stage_1 "1"      

$ST115_FMBN_ps
        stage           samples sequences taxa prop_samples prop_seq
stage_0 "original"      "63"    "2346838" "45" "1"          "1"     
stage_1 "prune samples" "63"    "2346838" "45" "1"          "1"     
        prop_taxa
stage_0 "1"      
stage_1 "1"      

$ST131_FMBN_ps
        stage           samples sequences taxa  prop_samples prop_seq
stage_0 "original"      "108"   "734976"  "302" "1"          "1"     
stage_1 "prune samples" "107"   "734493"  "302" "1"          "1"     
        prop_taxa
stage_0 "1"      
stage_1 "1"      

$ST136_FMBN_ps
        stage           samples sequences taxa prop_samples prop_seq
stage_0 "original"      "47"    "2759593" "60" "1"          "1"     
stage_1 "prune samples" "47"    "2759593" "60" "1"          "1"     
        prop_taxa
stage_0 "1"      
stage_1 "1"      
if(play_audio) beep(sound = 6)
if(keep_time) toc()
filter and glom samples: 0.767 sec elapsed

Taxonomic filtering.

A number of taxonomic filtering operations is performed at this stage (depending on the nature of the object and filtering options, set in the setting_options chunk). Plots are optionally produced and saved.

if(keep_time) tic("taxonomic filtering, step 1")

# check the nature of the taxonomic table
# in FMBN you either have ASV tables or tax tables with agglomeration at the species level or above
# which are the names of the tax levels?
tax_levels <- c("domain","phylum","class","order","family","genus","species")
# get/set rank names (this is necessary because phyloseq objects from FMBN or from the
# bioconductor pipeline with SILVA have different names)

physeq_list_2 <- lapply(physeq_list_1, gset_rank_names, tax_levels = tax_levels)
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
# remove uncharacterized taxa in all phyloseq objects (using a functional)
physeq_list_3 <- physeq_list_2
if(rm_unchar){
  physeq_list_3 <- lapply(physeq_list_2, subset_taxa, !is.na(domain) & !domain %in% c("", "uncharacterized"))
  physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(phylum) & !phylum %in% c("", "uncharacterized"))
}

# optionally remove Eukaryotes
if(rm_euk) {
  physeq_list_3 <- lapply(physeq_list_3, subset_taxa, domain !="Eukaryota")
}

# optionally remove chloroplasts and mitochondria 
if(rm_chlmit) {
  physeq_list_3 <- lapply(physeq_list_3, remove_Chl_Mit)
}

# use lookup table to change taxonomy of Lactobacillus; necessary for phyloseq objects produced with SILVA
# but not for objects extracted from FMBN (which are transformed before extraction); will also change the 
# taxa names for ASVs
# MAY BE SLOW
if(use_lookup){
  # loop over the list of phyloseq objects
  for(i in seq_along(physeq_list_3)){
    # check the length of the names of the taxa; if <100 it is from FMBN, break
    if(mean(sapply(taxa_names(physeq_list_3[[i]]), nchar),na.rm = T)<100){
      next
    } else {
      # change the names
      tnames <- str_c("ASV",seq(1:ntaxa(physeq_list_3[[i]])))
      taxa_names(physeq_list_3[[i]])<-tnames
      # get species to change
      taxa_table <- as.data.frame(as(tax_table(physeq_list_3[[i]]),"matrix"))
      taxa_table <- taxa_table %>% mutate(id = str_c(genus, species, sep = " "))
      taxa_table <- left_join(taxa_table, lookup_table)
      n_changes <- sum(!is.na(taxa_table$new_species))
      taxa_table <- taxa_table %>% mutate(species = if_else(!is.na(new_species), new_species, species),
                                          genus = if_else(!is.na(new_genus), new_genus, genus)
      )
      # remove columns
      taxa_table <- dplyr::select(taxa_table, domain:species)
      # now change genus for Lactobacillus with no species
      to_change_lb <- which((taxa_table$genus == "Lactobacillus") & is.na(taxa_table$species))
      taxa_table$genus[to_change_lb]<-"Lactobacillus complex"
      # replace Leuconostocaceae with Lactobacillaceae
      n_leuc <- nrow(dplyr::filter(taxa_table, family == "Leuconostocaceae"))
      taxa_table <- taxa_table %>% mutate(family = ifelse(family == "Leuconostocaceae", "Lactobacillaceae", family))
      taxa_table <- as.matrix(taxa_table)
      rownames(taxa_table)<-tnames
      tax_table(physeq_list_3[[i]])<-taxa_table
      if(verbose_output){
        cat(names(physeq_list_3)[i],": changed ", n_changes+n_leuc, " taxa\n", sep ="")
      }
    }
  }
}

# remove further taxa which are uncharacterized at the family to class level

# note for self: might improve it by setting the level at or above which uncharacterized taxa can be removed
# rather than using a T/F flag
if(above_genus_flag){
  physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(class) & !class %in% c("", "uncharacterized")) # Class
  physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(order) & !order %in% c("", "uncharacterized")) # Order
  physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(family) & !family %in% c("", "uncharacterized")) # Family
}

# optionally perform taxonomic agglomeration, 

# may take some time; can be made faster with plyr functions using parallelization or with furrr
physeq_list_4 <- physeq_list_3
if(taxglom != "none"){
  physeq_list_4 <- lapply(physeq_list_3, tax_glom_name_change, taxa_glom = taxglom)
}

# create third step of the filtering report
stage_name = "taxonomic filter+glom"
for(i in seq_along(physeq_list_0)){
  if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
    name <- names(physeq_list_0)[i]
    stage <- report_step_n(my_physeq = physeq_list_4[[name]], 
                           my_physeq_o = physeq_list_0[[i]], 
                           stage_name = stage_name) 
  } else {
    stage <- c(stage_name,
              samples = NA_real_,
              sequences = NA_real_,
              taxa = NA_real_,
              prop_samples = NA_real_,
              prop_seq = NA_real_,
              prop_taxa = NA_real_)
  }
  filtering_report[[i]] <- rbind(filtering_report[[i]], stage_3 =stage)
}

if(keep_time) toc()
taxonomic filtering, step 1: 17.067 sec elapsed
if(keep_time) tic("calculate diversity pre-filter")

# Calculate diversity prior to filtering for prevalence and abundance

div_est_prefilter <- map_dfr(physeq_list_4, phyloseq::estimate_richness, split = F, measure=c("Observed","Chao1","Shannon"))
The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.
div_est_prefilter <- mutate(div_est_prefilter, Pielou_J = Shannon/log(Observed))
# calculate and add ave Bray-Curtis dissimilarity
meanbcdist <- map(physeq_list_4, phyloseq::distance, method="bray")
div_est_prefilter$ave_BC <- unlist(map(meanbcdist, mean))

row.names(div_est_prefilter) <- names(physeq_list_4)
# save for further use
save(div_est_prefilter, 
     file = paste(file.path(output_folder,out_filename_pref), "_divprefilter.Rdata",sep=""))

if(keep_time) toc()
calculate diversity pre-filter: 0.22 sec elapsed
# Prevalence and abundance filter
if(keep_time) tic("taxonomic filtering, step 2")

# NOTE using a prevalence filter based on fraction may be wrong for studies with large 
# number of samples, in which one might want to retain taxa which appear in >10 samples 

physeq_list_5 <- physeq_list_4
# will be skipped if filterOTUs == F
node_stat_list <- vector("list", length = length(physeq_list_5))
# a list which will host node stats, like the prevab data, initially empty,
# if nothing is added at this stage, node stats will be added at a later stage
if(filterOTUs){
  prev_ab_list <- vector("list", length = length(physeq_list_4))
  # will host the lists with the results
  for(i in seq_along(physeq_list_4)){
    if(verbose_output) cat("prevalence and abundance filter, physeq ",i," of ",
                           length(physeq_list_4),"\n")
    prev_ab_list[[i]] <- filter_by_prev_ab(
      myphyseq = physeq_list_4[[i]],
      name = names(physeq_list_4)[i]
    )
    names(prev_ab_list)[i]<-names(physeq_list_4)[i]
    # save the processed phyloseq
    physeq_list_5[[i]] <- prev_ab_list[[i]][[1]]
    # save prev ab table
    node_stat_list[[i]] <- prev_ab_list[[i]][[4]]
    names(node_stat_list)[i] <- names(physeq_list_4)[i]
  }
}
prevalence and abundance filter, physeq  1  of  5 
Joining, by = "label"
Saving 7 x 7 in image
prevalence and abundance filter, physeq  2  of  5 
prevalence and abundance filter, physeq  3  of  5 
prevalence and abundance filter, physeq  4  of  5 
prevalence and abundance filter, physeq  5  of  5 
# optionally save the prev_ab_list
if(save_prev_ab_list) save(prev_ab_list, 
                           file = paste(file.path(output_folder,out_filename_pref), name, "_prevabl.Rdata",sep=""))

# create fourth step of the filtering report
stage_name = "prevalence and abundance"
for(i in seq_along(physeq_list_0)){
  if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
    name <- names(physeq_list_0)[i]
    stage <- report_step_n(my_physeq = physeq_list_5[[name]], 
                           my_physeq_o = physeq_list_0[[i]], 
                           stage_name = stage_name) 
  } else {
    stage <- c(stage_name,
              samples = NA_real_,
              sequences = NA_real_,
              taxa = NA_real_,
              prop_samples = NA_real_,
              prop_seq = NA_real_,
              prop_taxa = NA_real_)
  }
  filtering_report[[i]] <- rbind(filtering_report[[i]], stage_4 =stage)
}

# remove unneeded objects
rm(physeq_list_1, physeq_list_3, physeq_list_4, prev_ab_list, taxa_table)
# create a data frame with the report
filtering_report_df <- map_dfr(filtering_report, as.data.frame, .id = "dataset")
filtering_report_df <- filtering_report_df %>% 
  mutate(data_type = if_else(str_detect(dataset, "FMBN"), "FMBN", "ASV")) %>%
  mutate(dplyr::across(.cols = samples:prop_taxa, as.numeric)) %>%
  mutate(stage = as_factor(stage))
if(verbose_output) {
  print(filtering_report_df)
  print(filtering_report_df %>%
          dplyr::filter(stage != "original" & stage != "prune samples") %>%
          ggplot(mapping = aes(x = data_type, y = prop_taxa)) +
          facet_wrap(~stage) +
          geom_boxplot() +
          labs(x = "data type", y = "prop. taxa left"))
  summ_filtering <- filtering_report_df %>%
    group_by(data_type, stage) %>%
    summarize(min_seq = min(prop_seq), 
              max_seq = max(prop_seq),
              min_taxa = min(prop_taxa),
              max_taxa = max(prop_taxa))
  print(summ_filtering)
}

if(play_audio) beep(sound = 6)
if(keep_time) toc()
taxonomic filtering, step 2: 8.493 sec elapsed

The reduction in number of “taxa” is dramatic in most cases (a proportion from 0.010 to 0.21 left), while the proportion of sequences left is between 0.984 and 0.999.

Inferring the networks.

I will now try Microbial Association Network inference, with the methods and parameters specified in the options chunk. Separate lists will be generated for each method.
An error trapping routine has been implemented: if MAN estimation fails a try-error object rather than an object of class microNet will be returned.
All the returned objects will be saved in a list (1 slot for each phyloseq object, each with 1 slot for each inference method).

if(keep_time) tic("Microbial association network inference")
if(verbose_output) cat("Please be patient, this will take a while...\n")
Please be patient, this will take a while...
# list for results, 1 slot for each object
MAN_inf_results <- vector("list", length = length(physeq_list_5))
# create list for methods, 1 slot for each method
inf_meth_list <- vector("list", length = length(inf_methods))

for(i in seq_along(physeq_list_5)){
  name <- names(physeq_list_5)[i]
  if(verbose_output) cat("\n", "Inferring network(s) for ", name, ", ", i, "of ", 
                         length(physeq_list_5), "\n", sep=" ")
  if(keep_time) {
    ticmessage_object <- paste("microbial association network inference, ",
                               name, ", ", i, " of ", length(physeq_list_5), sep = "")
    tic(ticmessage_object)
  }
  for(j in seq_along(inf_methods)){
    if(keep_time){
      ticmessage_method <- paste("\n", "inference with method ",
                                 inf_methods[j], ", ", j, " of ", length(inf_methods), sep = "")
      tic(ticmessage_method)
    }
    infmethod <- inf_methods[[j]]
    infparam <- inf_methods_param[[j]]
   
    # infrence is carried out here using a user defined function loaded with the `source()` command 
    # you do not need to provide much detail, everything is taken care of in the options
    # of course could be customized in the function call
    inf_meth_list[[j]] <- infer_MAN(myphyseq = physeq_list_5[[i]],
                                    inf_method = infmethod,
                                    method_parameters = infparam)
    names(inf_meth_list)[j] <- infmethod
    if(keep_time) toc()
  }
  MAN_inf_results[[i]] <- inf_meth_list
  names(MAN_inf_results)[i] <- name
  if(keep_time) toc()
}

 Inferring network(s) for  ST106_FMBN_ps ,  1 of  5 
Infos about changed arguments:
Sparsification included in 'spieceasi'.

12 taxa and 30 samples remaining.
Optimal lambda may be larger than the supplied values

inference with method spieceasi, 1 of 4: 133.09 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.

12 taxa and 30 samples remaining.
1 job had warning: "There are variables in the data that have only zeros or only the same values."Optimal lambda may be larger than the supplied values

inference with method spring, 2 of 4: 90.672 sec elapsed
12 taxa and 30 samples remaining.

inference with method sparcc, 3 of 4: 1.403 sec elapsed
12 taxa and 30 samples remaining.
Feature(s) Halomonas, Loigolactobacillus, Tetragenococcus have more zeros than the threshold of 22 zeros.  Excluding from output (will still be used in normalizing.)All-zero subjects generated by permutation: 16.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 5.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 12.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 5.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 21.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 29.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 19.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 10.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 8.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 8.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 26.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 4.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 26.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 10.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 25.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 9.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 4.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 30.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 18.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 9.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 30.  These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1.  These will be replaced by all zeros after normalizationAssociation matrix contains NAs (replaced by zeros).

inference with method ccrepe, 4 of 4: 0.983 sec elapsed
microbial association network inference, ST106_FMBN_ps, 1 of 5: 226.151 sec elapsed

 Inferring network(s) for  ST110_FMBN_ps ,  2 of  5 
Infos about changed arguments:
Sparsification included in 'spieceasi'.

14 taxa and 112 samples remaining.

inference with method spieceasi, 1 of 4: 133.038 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.

14 taxa and 112 samples remaining.

inference with method spring, 2 of 4: 80.62 sec elapsed
14 taxa and 112 samples remaining.

inference with method sparcc, 3 of 4: 1.835 sec elapsed
14 taxa and 112 samples remaining.

inference with method ccrepe, 4 of 4: 3.845 sec elapsed
microbial association network inference, ST110_FMBN_ps, 2 of 5: 219.341 sec elapsed

 Inferring network(s) for  ST115_FMBN_ps ,  3 of  5 
Infos about changed arguments:
Sparsification included in 'spieceasi'.

8 taxa and 63 samples remaining.
Optimal lambda may be larger than the supplied values

inference with method spieceasi, 1 of 4: 159.202 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.

8 taxa and 63 samples remaining.
Optimal lambda may be larger than the supplied values

inference with method spring, 2 of 4: 85.27 sec elapsed
8 taxa and 63 samples remaining.

inference with method sparcc, 3 of 4: 1.215 sec elapsed
8 taxa and 63 samples remaining.
Feature(s) Staphylococcus have more zeros than the threshold of 54 zeros.  Excluding from output (will still be used in normalizing.)Association matrix contains NAs (replaced by zeros).

inference with method ccrepe, 4 of 4: 1.011 sec elapsed
microbial association network inference, ST115_FMBN_ps, 3 of 5: 246.699 sec elapsed

 Inferring network(s) for  ST131_FMBN_ps ,  4 of  5 
Infos about changed arguments:
Sparsification included in 'spieceasi'.

29 taxa and 107 samples remaining.
Optimal lambda may be larger than the supplied values

inference with method spieceasi, 1 of 4: 136.737 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.

29 taxa and 107 samples remaining.
47 jobs had warning: "'nearPD()' did not converge in 100 iterations"Optimal lambda may be larger than the supplied values

inference with method spring, 2 of 4: 606.343 sec elapsed
29 taxa and 107 samples remaining.

inference with method sparcc, 3 of 4: 2.445 sec elapsed
29 taxa and 107 samples remaining.
Feature(s) Acetitomaculum, Christensenellaceae_R-7_group, Clostridium, Corynebacterium, Enterococcus, Facklamia, Iamia, Lachnospiraceae_NK3A20_group, Loigolactobacillus, Luteimonas, NK4A214 group, Nocardioides, Paeniclostridium, Phascolarctobacterium, Planctomicrobium, Pseudomonas, Rikenellaceae_RC9_gut_group, Romboutsia, Ruminococcus, Staphylococcus, Treponema, Truepera, UCG-005 have more zeros than the threshold of 98 zeros.  Excluding from output (will still be used in normalizing.)Association matrix contains NAs (replaced by zeros).

inference with method ccrepe, 4 of 4: 1.791 sec elapsed
microbial association network inference, ST131_FMBN_ps, 4 of 5: 747.318 sec elapsed

 Inferring network(s) for  ST136_FMBN_ps ,  5 of  5 
Infos about changed arguments:
Sparsification included in 'spieceasi'.

11 taxa and 47 samples remaining.

inference with method spieceasi, 1 of 4: 150.238 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.

11 taxa and 47 samples remaining.

inference with method spring, 2 of 4: 84.859 sec elapsed
11 taxa and 47 samples remaining.

inference with method sparcc, 3 of 4: 1.467 sec elapsed
11 taxa and 47 samples remaining.

inference with method ccrepe, 4 of 4: 0.99 sec elapsed
microbial association network inference, ST136_FMBN_ps, 5 of 5: 237.56 sec elapsed
# create a report
inference_report <- vector("list", length(MAN_inf_results))
for(i in seq_along(MAN_inf_results)){
  inference_report[[i]]<-map_dfr(MAN_inf_results[[i]], class, .id = "method")
  names(inference_report)[i]<-names(MAN_inf_results)[i]
}
inference_report_df <- bind_rows(inference_report, .id = "dataset")

# save the list and do some clean-up
save(MAN_inf_results, file = file.path(output_folder, paste(out_filename_pref,"_MANlist.Rdata")))
rm(inf_meth_list, inference_report)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Microbial association network inference: 1677.632 sec elapsed

Analyze the networks.

The inferred networks will be analyzed using NetCoMi::netAnalyze() and the resulting objects will be processed further.
Network, node and edge stats will be extracted to data frames/tibbles for further processing.
A few extra analyses will be carried out using package phyloseq to add diversity and evenness indices to the metadata.


if(keep_time) tic("calculate diversity post-filter")

# estimate diversity for each object of the physeq_list_5, returns a list with the results
# extract and put together with metadata
# will generate warnings

div_est_postfilter <- map_dfr(physeq_list_5, estimate_richness, split = F, 
                              measures = c("Observed","Chao1","Shannon"), .id = "label")
The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.

We recommended that you find the un-trimmed data and retry.
div_est_postfilter <- mutate(div_est_postfilter, Pielou_J = Shannon/log(Observed))

# calculate and add average Bray-Curtis dissimilarity
meanbcdist <- map(physeq_list_5, phyloseq::distance, method="bray")
div_est_postfilter$ave_BC <- unlist(map(meanbcdist, mean))

row.names(div_est_postfilter) <- names(physeq_list_5)
# save for further use
save(div_est_postfilter, 
     file = paste(file.path(output_folder,out_filename_pref), "_divpostfilter.Rdata",sep=""))

# an alternative could be to use it on div_est_prefilter
study_metadata <- left_join(study_metadata, div_est_postfilter)
Joining, by = "label"
if(keep_time) toc()
calculate diversity post-filter: 0.257 sec elapsed
# calculate network statistics with netAnalyze
if(keep_time) tic("Calculate network statistics")

# the list for the methods within the dataset
net_stats <- vector("list", length = length(MAN_inf_results))
# net_stat_results is a list, do the calculation for each of the datasets, all inference methods
for(i in seq_along(MAN_inf_results)){
  name <- names(MAN_inf_results)[i]
  if(verbose_output) cat("Calculating network stats for",name,"\n")
  net_stat_results <- vector("list", length = length(MAN_inf_results[[i]]))
  for(j in seq_along(MAN_inf_results[[i]])){
    mthd <- names(MAN_inf_results[[i]])[j]
    if(verbose_output) cat("method",mthd,"\n")
    # consider reducing argument hubQuant to 0.75-0.90 default is 0.95),
    net_stat_results[[j]] <- calculate_net_stats(MAN_inf_results[[i]][[j]])
  }
  names(net_stat_results)<-names(MAN_inf_results[[i]])
  net_stats[[i]] <- net_stat_results
  names(net_stats)[i]<-names(MAN_inf_results)[i]
}
Calculating network stats for ST106_FMBN_ps 
method spieceasi 
method spring 
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE,  : 
  Network is empty.
method sparcc 
method ccrepe 
Calculating network stats for ST110_FMBN_ps 
method spieceasi 
method spring 
method sparcc 
method ccrepe 
Calculating network stats for ST115_FMBN_ps 
method spieceasi 
method spring 
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE,  : 
  Network is empty.
method sparcc 
method ccrepe 
Calculating network stats for ST131_FMBN_ps 
method spieceasi 
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE,  : 
  Network is empty.
method spring 
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE,  : 
  Network is empty.
method sparcc 
method ccrepe 
Calculating network stats for ST136_FMBN_ps 
method spieceasi 
method spring 
method sparcc 
method ccrepe 
rm(net_stat_results, meanbcdist, mthd)
if(keep_time) toc()
Calculate network statistics: 0.54 sec elapsed
# extracting global network properties

if(keep_time) tic("Extraction global network properties")
# extracting the global network stats 
global_props_list_a <-vector("list",length(net_stats))
global_props_list_l <-vector("list",length(net_stats)) 
global_props_list_all <-vector("list",length(inf_methods))
global_props_list_lcc <-vector("list",length(inf_methods))
for (i in seq_along(net_stats)){
  dataset <- names(net_stats)[i]
  for (j in seq_along(net_stats[[i]])){
    if(class(net_stats[[i]][[j]])!= "microNetProps"){
      next
    } else{
      nnodes <- sum(net_stats[[i]][[j]]$centralities$degree1>0)
      ntaxa <- nrow(net_stats[[i]][[j]]$input$assoMat1)
      nposedge <- sum(net_stats[[i]][[j]]$input$assoMat1[lower.tri(net_stats[[i]][[j]]$input$assoMat1)]>0)
      nnegedge <- sum(net_stats[[i]][[j]]$input$assoMat1[lower.tri(net_stats[[i]][[j]]$input$assoMat1)]<0)
      extra_prop_vector <- c(nnodes, ntaxa, nposedge, nnegedge)
      names(extra_prop_vector) <- c("nnodes", "ntaxa", "nposedge", "nnegedge")
      global_props_list_all[[j]] <- c(unlist(net_stats[[i]][[j]]$globalProps),extra_prop_vector)
      global_props_list_lcc[[j]] <- c(unlist(net_stats[[i]][[j]]$globalPropsLCC),extra_prop_vector)
      names(global_props_list_all)[j] <- names(global_props_list_lcc)[j]<- names(net_stats[[i]][j])
    }
    # create data frame with results
    all_df <- bind_rows(global_props_list_all, .id = "method")
    lcc_df <- bind_rows(global_props_list_lcc, .id = "method") 
  }
  global_props_list_a[[i]] <- all_df
  global_props_list_l[[i]] <- lcc_df
  names(global_props_list_a)[i] <- names(global_props_list_l)[i] <- names(net_stats)[i]
}
# note that str_sub only works if you have <=9 inference methods in inf_methods
global_all_df <- bind_rows(global_props_list_a, .id = "dataset") 
global_lcc_df <- bind_rows(global_props_list_l, .id = "dataset") 


global_all_df <- left_join(global_all_df, 
                           select(study_metadata,label, obj_type, target, 
                                  region, platform, samples, type, Observed, 
                                  Chao1, Shannon, Pielou_J, ave_BC), 
                           by = c("dataset" = "label")) %>%
  mutate(across(where(is.numeric), ~na_if(.,Inf)))
global_lcc_df <- left_join(global_lcc_df, 
                           select(study_metadata,label, obj_type, target, 
                                  region, platform, samples, type, Observed, 
                                  Chao1, Shannon, Pielou_J, ave_BC), 
                           by = c("dataset" = "label")) %>%
  mutate(across(where(is.numeric), ~na_if(.,Inf)))

# both might contain Inf which are replaced by NA (using dplyr::na_if()) to be handled
# correctly if doing PCA by pairwise deletion.
# the problem only occurs in avPath1 and clustCoef1, but I am handling it with a scoped mutate
# a better solution might be the use of hablar::rationalize()


# save the data frames for further use
write_tsv(global_all_df, file = paste(file.path(output_folder,out_filename_pref), "_netpropall.txt",sep=""))
write_tsv(global_lcc_df, file = paste(file.path(output_folder,out_filename_pref), "_netproplcc.txt",sep=""))

# print a summary table
global_all_df

rm(global_props_list_a, global_props_list_l, global_props_list_all, 
   global_props_list_lcc, all_df, lcc_df, nnodes, ntaxa, nposedge, nnegedge, extra_prop_vector)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Extraction global network properties: 0.932 sec elapsed

Global network properties have been saved as data frames for further use. They will be used together those generated for the other data sets.

Node properties.

Node properties can be extracted from the microNetProps objects and saved for further use.


# extract node properties
# using a loop takes slightly longer that using functionals but handles names better
# note that when using ASVs comparing nodes between datasets does not make much sense

if(keep_time) tic("Extracting node properties")

node_stats <- vector("list", length = length(net_stats))
for (i in seq_along(net_stats)) {
  node_properties <- node_stat_list[[i]]
  
  if(verbose_output) cat("extracting node stats for", names(net_stats)[i],"\n")
  for (j in seq_along(net_stats[[i]])) {
    if (class(net_stats[[i]][[j]]) == "microNetProps") {
      node_stats[[i]][[j]] <- extract_node_stats(net_stat_list = net_stats[[i]][[j]], 
                                                 nodestat = node_properties)
      method <- names(net_stats[[i]])[j]
      dataset <- names(net_stats)[i]
      nrows <- nrow(node_stats[[i]][[j]])
      node_stats[[i]][[j]] <- bind_cols(
        dataset = rep(dataset,nrows),
        method = rep(method,nrows),
        node_stats[[i]][[j]]
        )
    } else {
      cat("no node stats to return for",
          names(net_stats)[i],
          names(node_stats[[i]])[j],
          "\n")
      next
    }
  }
  node_stats[[i]]<-bind_rows(node_stats[[i]])
}
extracting node stats for ST106_FMBN_ps 
Joining, by = "label"
no node stats to return for ST106_FMBN_ps 
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST110_FMBN_ps 
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST115_FMBN_ps 
Joining, by = "label"
no node stats to return for ST115_FMBN_ps 
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST131_FMBN_ps 
no node stats to return for ST131_FMBN_ps 
no node stats to return for ST131_FMBN_ps 
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST136_FMBN_ps 
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
node_stats_df <- bind_rows(node_stats)
# perform some tidying
node_stats_df <- node_stats_df %>%
  tidyr::separate(dataset, into = c("Study", "Accn_n", "suf"), sep = "_", remove = F) %>% 
  dplyr::select(-Accn_n, -suf) %>%
  mutate(label2 = if_else(!str_detect(dataset, "FMBN"),str_c(label, Study, sep = "_"), label))

# label2 is only necessary when using ASVs or OTUs, not if there has been taxonomic agglomeration
# consider removing the mutate instruction

write_tsv(node_stats_df, file = paste(file.path(output_folder,out_filename_pref), "_nodestats_df.txt",sep=""))
rm(node_stats)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Extracting node properties: 0.725 sec elapsed

Edge properties.

Edges and edge properties can also be extracted for further use. Important edge properties are:

  • edge type (positive or negative)

  • edge weight and association measure

  • edge betweenness

It is also convenient to compare edges among different graphs (and to do so it might be convenient to make sure that “to” and “from” are always in alphabetical order, which is always true within the same graph but might not be necessarily true among different graphs).
The following chunk will (optionally):

  • integrate node (calculated with netAnalyse()) in the tidygraph list,

  • calculate edge betweenness,

  • compare networks (within dataset) by producing and saving Venn diagrams of the edges

Finally, a data frame with all edges will be produced for further use.


if(keep_time) tic("List with tidygraph objects created")

# creating a tidygraph object for each net
tidygraph_list <- vector("list", length = length(MAN_inf_results))

for(i in seq_along(MAN_inf_results)){
  if(verbose_output) cat("Converting in tidygraphs for", names(MAN_inf_results)[i],"\n")
  # the warning, if any, is not very informative, should consider passing names of datasets and methods
  tidygraph_list[[i]] <- MAN_inf_results[[i]] %>% map(microNet_to_tidygraph, fail_w_err = F, use_asso_matrix = T)
}
Converting in tidygraphs for ST106_FMBN_ps 
Joining, by = c("from", "to")
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST110_FMBN_ps 
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST115_FMBN_ps 
Joining, by = c("from", "to")
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST131_FMBN_ps 
the network has 0 edges
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST136_FMBN_ps 
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
names(tidygraph_list)<-names(MAN_inf_results)
if(keep_time) toc()
List with tidygraph objects created: 0.997 sec elapsed
# optionally merge further node stats (depends on merge_n_stats) 
# and calculate edge betweenness (depends on calc_e_betw)
# I am using a loop
if(keep_time) tic("Stats added to tidygraphs, edge dataframe created")
tidygraph_list_wstats <- vector("list", length = length(tidygraph_list))
# the list with the edge data frames
edge_list <- vector("list", length = length(tidygraph_list))

for(i in seq_along(tidygraph_list_wstats)){
  # need to be reset
  inner_tgl <- vector("list", length = length(tidygraph_list[[i]]))
  inner_el <- vector("list", length = length(tidygraph_list[[i]]))
  dtst <- names(tidygraph_list)[i]
  for(j in seq_along(inner_tgl)){
    if(is.tbl_graph(tidygraph_list[[i]][[j]])){
    mthd = names(tidygraph_list[[i]])[j]
    nstats <- node_stats_df %>% dplyr::filter(dataset == dtst & method == mthd)
    inner_tgl[[j]] <- merge_stats(tg = tidygraph_list[[i]][[j]],
                             node_stats = nstats, 
                             ebetw = calc_e_betw)
     # extract edge tibble
    inner_el[[j]] <- inner_tgl[[j]] %>% 
      activate(edges) %>% 
      as_tibble() %>%
      mutate(method = mthd)
    # do naming
    names(inner_tgl)[j] <- names(inner_el)[j] <- names(tidygraph_list[[i]])[j]
    } else {
      next
    }
  }
  tidygraph_list_wstats[[i]] <- inner_tgl
  edge_list[[i]] <- bind_rows(inner_el)
  edge_list[[i]] <- edge_list[[i]] %>% 
    mutate(dataset = dtst) %>% dplyr::select(dataset, method, !(dataset:method))
  names(tidygraph_list_wstats)[i] <- names(edge_list)[i] <- names(tidygraph_list)[i]
}
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
rm(nstats)
# build and fix the edge df
edge_list_df <- bind_rows(edge_list)

edge_list_df <- edge_list_df %>% 
  mutate(name_from_sorted = if_else(from_name < to_name, from_name, to_name),
         name_to_sorted = if_else(from_name < to_name, to_name, from_name)) %>%
  mutate(edge_name = str_c(name_from_sorted, name_to_sorted, sep = "--"))
write_tsv(edge_list_df, file = paste(file.path(output_folder,out_filename_pref), "_edgelist_df.txt",sep=""))
if(keep_time) toc()
Stats added to tidygraphs, edge dataframe created: 1.145 sec elapsed
# make Venn plots

# I am using a loop
if(do_Venn){
  if(keep_time) tic("Venn plots created and saved")
  Venn_list <- vector("list", length(tidygraph_list))
  for(i in seq_along(tidygraph_list)){
    # need to select only elements of class tbl_graph
    tblgrphs <- map_lgl(tidygraph_list[[i]], is.tbl_graph)
    names(Venn_list) <- names(tidygraph_list)
    if(length(tidygraph_list[[i]][tblgrphs])>1){
      inner_list <- map(tidygraph_list[[i]][tblgrphs], function(x) as_ids(E(x)))
      Venn_title <- names(tidygraph_list)[i]
      Venn_file <- paste(file.path(output_folder,out_filename_pref),"_",Venn_title, "_venns.tiff",sep="")
      my_fill <- (2:5)[1:length(tidygraph_list[[i]][tblgrphs])]
      Venn_list[[i]] <- venn.diagram(inner_list, 
                                       fill = my_fill, 
                                       alpha = 0.3, 
                                       filename = Venn_file, 
                                       margin = 0.05, 
                                       main = Venn_title, 
                                       main.cex = 1.5, 
                                       main.fontface = "bold", 
                                       main.fontfamily = "sans", 
                                       main.pos = c(0.5, 1.05)
        )
    }
  }
  rm(inner_list, Venn_title, Venn_file)
  if(keep_time) toc()
}
Venn plots created and saved: 3.538 sec elapsed
if(play_audio) beep(sound = 6)

Looking at the Venns it can be seen that the sparsest networks are almost always produced by SPIEC-EASI and that the number of edges shared by all methods for any given is very low.

Comparing global properties.

Comparing global network properties may be of assistance in evaluating the effect of the inference method or of the type of study. Although NetCoMi offers very effective tools to compare two networks, it does not easily generalize to more than 2-3 networks. Here, I will use the global_all_df and global_lcc_df and produce graphs using PCA.
Note line 1051 should be manually adapted to show all loadings and scores

if(keep_time) tic("Comparing global properties")
# create a label and extract matrix for the analysis
global_all_df_2 <- global_all_df %>%
  tidyr::separate(dataset, into = c("study", "type", "object"), remove = F) %>%
  select(-type, -object) %>%
  mutate(method_brief = case_when(
    method == "spieceasi" ~ "spi",
    method == "spring" ~ "spr",
    method == "ccrepe" ~ "ccr",
    method == "sparcc" ~ "spa"
  )) %>%
  tidyr::unite(label, study, method_brief, sep = "_", remove = F)


# keep only relevant columns and make a matrix
global_all_df_mat <- global_all_df_2 %>% 
  dplyr::select(label, nComp1:nnegedge, samples, Chao1, Pielou_J, ave_BC) %>%
  column_to_rownames("label") %>% as.matrix()

# optionally obtain a scatterplot matrix
if(verbose_output) ggpairs(as.data.frame(global_all_df_mat), progress = F)



# look at the number of components (using correlation matrix)
var_to_use <- !(colnames(global_all_df_mat) %in% c("ncomp", "vertConnect1","edgeConnect1","ntaxa"))
psych::fa.parallel(global_all_df_mat[,c(1:5,8:14,17:18)], fa = "pc", n.iter = 100)
Matrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneThe estimated weights for the factor scores are probably incorrect.  Try a different factor score estimation method.
Parallel analysis suggests that the number of factors =  NA  and the number of components =  2 

PCA1 <- psych::principal(global_all_df_mat[,c(1:5,8:14,17:18)], nfactors = 2, rotate = "varimax")
Matrix was not positive definite, smoothing was done
PCA1
Principal Components Analysis
Call: psych::principal(r = global_all_df_mat[, c(1:5, 8:14, 17:18)], 
    nfactors = 2, rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix

                       RC1  RC2
SS loadings           5.24 3.26
Proportion Var        0.37 0.23
Cumulative Var        0.37 0.61
Proportion Explained  0.62 0.38
Cumulative Proportion 0.62 1.00

Mean item complexity =  1.3
Test of the hypothesis that 2 components are sufficient.

The root mean square of the residuals (RMSR) is  0.13 
 with the empirical chi square  60.72  with prob <  0.59 

Fit based upon off diagonal values = 0.9
biplot(PCA1, xlim.s = c(-1,4), ylim.s = c(-2,5), main = "PCA biplot, all variables")

fullnetscores <- cbind(global_all_df_2,PCA1$scores)
# the score plot
ggplot(fullnetscores, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
  geom_point() +
  labs(title = "all variables") +
  scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
  scale_color_brewer(type = "qual", palette = "Paired") +
  theme(plot.title = element_text(hjust = 0.5))


var_to_use <- colnames(global_all_df_mat) %in% c("ncomp1", "modularity1","density1","clustCoef1",
                                                 "avPath1", "pep1", "Pielou_J", "ave_BC", "nnodes")
cat("variables to use: ", var_to_use, "\n")
variables to use:  FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE TRUE TRUE 
psych::fa.parallel(global_all_df_mat[,var_to_use], fa = "pc", n.iter = 100)
The estimated weights for the factor scores are probably incorrect.  Try a different factor score estimation method.An ultra-Heywood case was detected.  Examine the results carefully
Parallel analysis suggests that the number of factors =  NA  and the number of components =  2 

PCA2 <- principal(global_all_df_mat[,var_to_use], nfactors = 2, rotate = "varimax")
PCA2
Principal Components Analysis
Call: principal(r = global_all_df_mat[, var_to_use], nfactors = 2, 
    rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix

                       RC1  RC2
SS loadings           2.66 2.28
Proportion Var        0.33 0.29
Cumulative Var        0.33 0.62
Proportion Explained  0.54 0.46
Cumulative Proportion 0.54 1.00

Mean item complexity =  1.2
Test of the hypothesis that 2 components are sufficient.

The root mean square of the residuals (RMSR) is  0.12 
 with the empirical chi square  15.18  with prob <  0.3 

Fit based upon off diagonal values = 0.89
biplot(PCA2)

fullnetscores_2 <- cbind(global_all_df_2,PCA2$scores)
var_acc_RC1 <- round(PCA2$Vaccounted[4,1],3)
var_acc_RC2 <- round(PCA2$Vaccounted[4,2],3)
# the score plot
ggplot(fullnetscores_2, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
  geom_point() +
  labs(title = "selected variables") +
  scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
  scale_color_brewer(type = "qual", palette = "Paired") +
  labs(x = paste("RC1 (", var_acc_RC1, ")", sep =""),
       x = paste("RC2 (", var_acc_RC2, ")", sep ="")) +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5))


ggsave(filename = paste(file.path(output_folder,out_filename_pref), "_PCA.tiff",sep=""), device = "tiff", width = 6, 
       height = 5, units = "in", dpi=600)


if(keep_time) toc()
Comparing global properties: 57.722 sec elapsed

With these many datasets and inference methods the PCA becomes difficult to use. Note how the properties of the network are, as expected, specific to any given inference methods and data-set and how methods based on correlation (sparCC and CCREPE) tend to be closer among them than to methods based on conditional dependence.

The analysis is repeated below on the largest connected component for each network.

if(keep_time) tic("Comparing global properties on LCC")
# create a label and extract matrix for the analysis
global_all_df_3 <- global_lcc_df %>%
  tidyr::separate(dataset, into = c("study", "type", "object"), remove = F) %>%
  select(-type, -object) %>%
  mutate(method_brief = case_when(
    method == "spieceasi" ~ "spi",
    method == "spring" ~ "spr",
    method == "ccrepe" ~ "ccr",
    method == "sparcc" ~ "spa"
  )) %>%
  tidyr::unite(label, study, obj_type, method_brief, sep = "_", remove = F)


# keep only relevant columns and make matrix
global_all_df_mat_2 <- global_all_df_3 %>% 
  dplyr::select(label, lccSize1:nnegedge, samples, Chao1, Pielou_J, ave_BC) %>%
  column_to_rownames("label") %>% as.matrix()
# need to remove an Inf value, I am removing the row

# obtain a scatterplot matrix
if(verbose_output) ggpairs(as.data.frame(global_all_df_mat_2[-3,]), progress = F)



# look at the number of components (using correlation matrix): must exclude
# ave path length and clustering coefficient which contain Inf and NaN

psych::fa.parallel(global_all_df_mat_2[-3,c(1:3,6,9:14,17:18)], fa = "pc", n.iter = 100)
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
In factor.stats, the correlation matrix is singular, an approximation is used
The estimated weights for the factor scores are probably incorrect.  Try a different factor score estimation method.In factor.scores, the correlation matrix is singular, an approximation is used
I was unable to calculate the factor score weights, factor loadings used instead
Parallel analysis suggests that the number of factors =  NA  and the number of components =  1 

PCA1_lcc <- psych::principal(global_all_df_mat_2[-3,c(1:3,6,9:14,17:18)], nfactors = 2, rotate = "varimax")
The determinant of the smoothed correlation was zero.
This means the objective function is not defined.
Chi square is based upon observed residuals.
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
In factor.stats, the correlation matrix is singular, an approximation is used
The matrix is not positive semi-definite, scores found from Structure loadings
PCA1_lcc
Principal Components Analysis
Call: psych::principal(r = global_all_df_mat_2[-3, c(1:3, 6, 9:14, 
    17:18)], nfactors = 2, rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix

                       RC1  RC2
SS loadings           5.90 2.58
Proportion Var        0.49 0.21
Cumulative Var        0.49 0.71
Proportion Explained  0.70 0.30
Cumulative Proportion 0.70 1.00

Mean item complexity =  1.1
Test of the hypothesis that 2 components are sufficient.

The root mean square of the residuals (RMSR) is  0.13 
 with the empirical chi square  42.15  with prob <  0.51 

Fit based upon off diagonal values = 0.93
biplot(PCA1_lcc, main = "PCA biplot, all variables")

fullnetscores_lcc <- cbind(global_all_df_3[-3,],PCA1_lcc$scores)
# the score plot
ggplot(fullnetscores_lcc, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
  geom_point() +
  labs(title = "all variables") +
  scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
  scale_color_brewer(type = "qual", palette = "Paired") +
  theme(plot.title = element_text(hjust = 0.5))


var_to_use_lcc <- colnames(global_all_df_mat) %in% c("lccSize1", "modularity1","density1", "pep1", "Pielou_J", "ave_BC", "nnodes")
cat("variables to use: ", var_to_use_lcc, "\n")
variables to use:  FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE TRUE TRUE 
psych::fa.parallel(global_all_df_mat_2[-3,var_to_use_lcc], fa = "pc", n.iter = 100)
The estimated weights for the factor scores are probably incorrect.  Try a different factor score estimation method.
Parallel analysis suggests that the number of factors =  NA  and the number of components =  1 

PCA2_lcc <- principal(global_all_df_mat_2[-3,var_to_use_lcc], nfactors = 2, rotate = "varimax")
PCA2_lcc
Principal Components Analysis
Call: principal(r = global_all_df_mat_2[-3, var_to_use_lcc], nfactors = 2, 
    rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix

                       RC1  RC2
SS loadings           2.14 2.12
Proportion Var        0.36 0.35
Cumulative Var        0.36 0.71
Proportion Explained  0.50 0.50
Cumulative Proportion 0.50 1.00

Mean item complexity =  1.2
Test of the hypothesis that 2 components are sufficient.

The root mean square of the residuals (RMSR) is  0.11 
 with the empirical chi square  6.1  with prob <  0.19 

Fit based upon off diagonal values = 0.93
biplot(PCA2_lcc)

fullnetscores_2_lcc <- cbind(global_all_df_2[-3,],PCA2_lcc$scores)
# the score plot
var_acc_RC1_lcc <- round(PCA2$Vaccounted[4,1],3)
var_acc_RC2_lcc <- round(PCA2$Vaccounted[4,2],3)
# the score plot
ggplot(fullnetscores_2_lcc, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
  geom_point() +
  labs(title = "selected variables") +
  scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
  scale_color_brewer(type = "qual", palette = "Paired") +
  labs(x = paste("RC1 (", var_acc_RC1_lcc, ")", sep =""),
       x = paste("RC2 (", var_acc_RC2_lcc, ")", sep ="")) +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5))

ggsave(filename = paste(file.path(output_folder,out_filename_pref), "_PCA_lcc.tiff",sep=""), device = "tiff", width = 6, 
       height = 5, units = "in", dpi=600)


if(keep_time) toc()
Comparing global properties on LCC: 73.565 sec elapsed

The results are similar, and there is not much to add. While SPIEC-EASI produces the most sparse networks (and can be a candidate for the detection of the most parsimonious interaction sets), CCREPE and SparCC both may detect indirect interactions and, detecting clusters in this networks may be useful to detect sub-biomes within a given study.

Plotting selected networks.

I will now use tidygraph and ggraph to plot the networks. The thickness of the edges is made proportional to the absolute value of the association measure. Copresence edges are in green, mutual exclusion ones in red. Size of the nodes is made proportional to the total degree (negative degree + positive degree). The color of the nodes is determined by the phylum. Some of this options can be adjusted in the call to the plot_ggraph() function below. Others must be adjusted in the code of the function (all functions are in the source folder).

if(keep_time) tic("Plotting the networks with ggraph")
# create a list of network plots
netplot_list <- vector("list", length = length(tidygraph_list_wstats))
for(i in seq_along(tidygraph_list_wstats)){
  netplot_list_2 <- vector("list", length = length(tidygraph_list_wstats[[i]]))
  dtst <- names(tidygraph_list_wstats)[i]
  for(j in seq_along(tidygraph_list_wstats[[i]])){
    if(!is.tbl_graph(tidygraph_list_wstats[[i]][[j]])){
      netplot_list_2[[j]] <- "no plot to return"
      next
    } else {
      tg <- tidygraph_list_wstats[[i]][[j]]
      mthd <- names(tidygraph_list_wstats[[i]])[j]
      # the default for the argument c0l0r is phylum, otherwise
      # use c0l0r = clust_memb
      # argument lp = "bottom" is the default; "right" is an alternative
      netplot_list_2[[j]] <- plot_ggraph(tidy_graph = tg,
                                         method = mthd,
                                         name = dtst,
                                         lp = "right")
      names(netplot_list_2)[j] <- mthd
      print(netplot_list_2[[j]])
    }
  }
  netplot_list[[i]] <- netplot_list_2
  names(netplot_list)[i] <- dtst
}


rm(tg, dtst, mthd, netplot_list_2)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Plotting the networks with ggraph: 12.194 sec elapsed

Due to the large number of interactions it is hard to discuss the results. In some of the graphs clusters are clearly evident (and could be highlighted by using the cluster id for colors). The structure of the graphs obtained with the different methods for each given dataset is also different. This may be due to the fact that both the correlation based methods may detect indirect interactions.
In addition, given the scope of this analysis (with emphasis on nodes and edges conserved across different studies) there is little point in discussing individual networks, because this would require going into detail in the experimental approach used in each study.
The following chunk is designed to compare network plots built with different methods in a grid. Two of the datasets (chosen because they returned a network for all inference methods and because they had networks of different complexity have been chosen here). However, due to the large number of elements in the graph, plotting all the elements (graph, legend, title) is difficult and may require acting on graphic parameters.

if(keep_time) tic("Plotting and saving networks in a grid")

p1<-netplot_list[[2]][[1]]
p2<-netplot_list[[2]][[2]]
p3<-netplot_list[[2]][[3]]
p4<-netplot_list[[2]][[4]]
ggrid_1 <- egg::ggarrange(p1, p2, p3, p4, nrow = 2, ncol = 2)
fn <- paste(file.path(output_folder,out_filename_pref), "_ST110.tiff",sep="")
ggsave(ggrid_1, filename = fn, dpi = 600, width = 7, height = 7)
ggrid_1

p5<-netplot_list[[9]][[1]]
p6<-netplot_list[[9]][[2]]
p7<-netplot_list[[9]][[3]]
p8<-netplot_list[[9]][[4]]
ggrid_2 <- egg::ggarrange(p5, p6, p7, p8, nrow = 2, ncol = 2)
fn <- paste(file.path(output_folder,out_filename_pref), "_ST136.tiff",sep="")
ggsave(ggrid_2, filename = fn, dpi = 600, width = 7, height = 7)
ggrid_2

if(play_audio) beep(sound = 6)
if(keep_time) toc()
rm(p1, p2, p3, p4, p5, p6, p7, p8)

Node analysis.

When comparing networks from several studies it might be of interest to classify nodes on the basis of their measures of centrality across multiple studies: is there a super hub (i.e. a node which i a hub in several networks)? How are different centrality measures related? Are there nodes which engage more often in negative interactions? Of course it would be pointless to compare across several methods of inference or, given the results above, between ASV and FMBN studies (when aggregation at the genus level is used). This script will produce a few node plots as a proof of concept. Both SPIEC-EASI and SparCC will be used on data extracted from FMBN and aggregated at the genus level.

if(keep_time) tic("Node analysis")

mymethods <- c("spieceasi","sparcc")
n_to_label <- 20 # max number of nodes to label in the node plots
node_analysis_list <- vector("list", length = length(mymethods))
for(i in seq_along(mymethods)){
  # create the dfs, one for spiec-easi and one for sparCC and only select FMBN
  # only select nodes with degree>0
  FMBN_node_df <- node_stats_df %>%
    dplyr::filter(method == mymethods[i]) %>%
    dplyr::filter(degree>0) %>%
    mutate(Study = as.factor(Study),
           rel_pos_degree = pos_degree / sum(pos_degree)) %>%
    dplyr::rename(tlabel = label)
  
  
  # use a loop to do the node degree graphs, print them and put them in a list
  node_degree_plot_list <- vector("list", length=nlevels(FMBN_node_df$study))
  
  for(j in seq_along(levels(FMBN_node_df$Study))){
    gtitle = paste(levels(FMBN_node_df$Study)[j], mymethods[i], sep = ", ")
    temp_df <- FMBN_node_df %>% 
      dplyr::filter(Study ==levels(FMBN_node_df$Study)[j]) %>%
      mutate(dgr = pos_degree + neg_degree) %>%
      arrange(-dgr) %>%
      rowid_to_column() %>% 
      mutate(to_label = if_else((rowid<=20 | is_hub), tlabel, NA_character_))
    
    ave_degree = mean(temp_df$dgr, na.rm = T)
    ave_pos_degree = mean(temp_df$pos_degree, na.rm = T)
    # creating the plot, only the names of the top 20 nodes (in terms of degree)
    # are plotted, hubs are always plotted
    ggp <- ggplot(
      temp_df,
      mapping = aes(
        x = pos_degree,
        y = dgr,
        label = str_trunc(genus, 12, side = "center")
      )
    ) +
      geom_smooth(method = "lm", linetype = 3, se = F, color = I("black"), show.legend = F) +
      geom_point(mapping = aes(color = phylum,
                               size = relAbundance,
                               alpha = between)) +
      geom_text_repel(show.legend = F, max.overlaps = 20, alpha = I(0.5)) +
      geom_abline(slope = 1, intercept = 0, linetype = 1) +
      geom_hline(yintercept = ave_degree, linetype = 3, show.legend = F) +
      geom_vline(xintercept = ave_pos_degree, linetype = 3, show.legend = F) +
      scale_alpha_continuous(range = c(0.4, 1)) +
      scale_size_continuous(range = c(1,6)) +
      labs(
        x = "positive degree",
        y = "degree",
        size = "relative abundance",
        alpha = "betweenness",
        title = gtitle
      ) +
      theme_bw() +
      theme(plot.title = element_text(hjust = 0.5))
    
    print(ggp)
    node_degree_plot_list[[j]] <- ggp
    names(node_degree_plot_list[[j]]) <- gtitle
  }
  file_name <- paste(file.path(output_folder,out_filename_pref), "_", mymethods[i], "_nodeplots.Rdata",sep="")
  save(node_degree_plot_list, file = file_name)
  
  # calculate frequency for hubs (meaningful only if you have many taxa and the percentile for hub detection is low, say 0.75-0.90)
  n_datasets <- dplyr::n_distinct(FMBN_node_df$dataset)
  FMBN_node_df_hubs <- FMBN_node_df %>%
    dplyr::filter(is_hub == T) %>%
    group_by(tlabel, phylum, class) %>%
    summarise(hub_freq = n()/n_datasets) %>%
    arrange(-hub_freq)
  cat("method", mymethods[i], "hub frequency", "\n")
  head(FMBN_node_df_hubs, 20)
  # save elements in the list
node_analysis_list[[i]] <- list(
  node_df = FMBN_node_df,
  plot_list = node_degree_plot_list,
  node_df_hubs = FMBN_node_df_hubs
)
names(node_analysis_list)[i]<- mymethods[i]
  
}
method spieceasi hub frequency 
method sparcc hub frequency 

if(play_audio) beep(sound = 6)
if(keep_time) toc()
Node analysis: 10.47 sec elapsed
rm(FMBN_node_df, node_degree_plot_list, temp_df, FMBN_node_df_hubs)

The node plots provide a number of visual cues:

  • the horizontal and vertical dotted lines show the average value for degree and positive degree and may help in identifying nodes whose values are far from the mean;

  • the diagonal continuous line corresponds to points for which positive degree and degree are equal; points above the line have at least 1 negative interaction

  • the diagonal dotted line is the linear regression line for degree as a function of positive degree; its position and slope relative to the previous line indicates on average how much the ratio between total degree and positive degree changes as a function of degree; data points which are far from this line also have an unusual ratio between the two values compared to the other nodes in the same dataset.

  • negative hubs will be located close to the upper left corner; positive hubs will be closer to the right of the plot.

Even with SPIEC-EASI the number of plotted nodes is high for some studies (but very low for others), and one may consider plotting a given number of nodes (the ones with top degree? top eigenvector centrality?, some combination of both) to simplify the plot.
Said that, although betweenness centrality may be interesting to identify “bottleneck” taxa, looking at betweenness is difficult (the shades of alpha = transparency are visually difficult to separate), but otherwise degree, positive degree and abundance are all easy to visualise.

Edge analysis.

Edge analysis only makes sense once one has settled for meaningful questions and selected a large and diverse enough set of studies. Questions which might be relevant:

  • how stable is an edge?

    • within a study across methods

    • across studies with a given method

  • given an inference method, can one assemble a consensus network using as weights the number of times a given edge occurs? The frequency of occurrence?

  • how important is an edge (as measured by edge betweenness)? One must choose a meaningful context for this

  • are co-occurrence relationships more frequent among members of the same class or family (and the reverse for mutual exclusion)? This only makes sense within a given method and must be corrected by the frequency of ech given larger taxon.

  • given a (possibly important) microorganism, with which microorganisms is it most frequently associated with positive or negative associations? This only makes sense within a given method and once one has analyzed a large number of studies

  • for moderately large networks, which is the cumulative distribution of edge betweenness? Does this hold any importance in terms of structure of the networks?

if(keep_time) tic("Edge analysis")
# this calculates the empirical quantile of edge betweenness
# by dataset and method: I suppose one can then select those >0.95
# to get a sort of hubness for edges, but it makes sense only for 
# large number of edges
edge_list_df <- edge_list_df %>% group_by(dataset, method) %>%
  mutate(ebq = ecdf(edge_betw)(edge_betw)) %>% ungroup()

# calculate the frequency of edges, by study and type and 
# median value and IQR for IQR
# this only checks for conserved edges across methods
edge_freq_bystudy <- edge_list_df %>% 
  group_by(dataset, asso_type, edge_name) %>% 
  summarise(n = n(),
            med_ebq = median(ebq),
            iqr_ebq = IQR(ebq)) %>%
  arrange(-n, -med_ebq, ) %>% 
  ungroup()
`summarise()` has grouped output by 'dataset', 'asso_type'. You can override using the `.groups` argument.
# I am now generating a plot may be with the top 50 edges, only for FMBN studies
edge_freq_bystudy_FMBN <- edge_freq_bystudy %>%
  dplyr::filter(str_detect(dataset, "FMBN")) %>%
  arrange(-n, -med_ebq)

# let's try a plot (gives an emphasis to most stable edges)
slice_to_plot <- slice(edge_freq_bystudy_FMBN, 1:50) %>%
    mutate(edge_name = forcats::fct_reorder(edge_name, med_ebq)) 

ggplot(slice_to_plot, 
       mapping = aes(x = edge_name, y = med_ebq, size = n, color = asso_type)) +
  geom_point() + 
  coord_flip() +
  labs(x = "edge", y = "edge betweenness quantile",
      size = "edge freq.") +
    scale_colour_manual(values = c("green","red"))



# another way to see it, based on stability
slice_to_plot_2 <- slice(edge_freq_bystudy_FMBN, 1:50) %>%
    mutate(edge_name = forcats::fct_reorder(edge_name, n))

ggplot(slice_to_plot_2, 
       mapping = aes(x = edge_name, y = n, size = med_ebq, color = asso_type)) +
  geom_point() + 
  coord_flip() +
  labs(x = "edge", y = "edge freq.",
      size = str_wrap("edge betweenness quantile", 15)) +
    scale_colour_manual(values = c("green","red"))

fn <- paste(file.path(output_folder,out_filename_pref), "_edge_n.tiff",sep="")
ggsave(filename = fn, dpi = 600, width = 10, height = 7)

Many of the most stable edges include gut bacteria and probably represent cluster of interactions from this environment which are carried over to milk and then, possibly, cheese (some studies analyzed here include also environmental samples, like feces).
The analysis here was carried out by groping by dataset and association type, therefore the frequency is by dataset and a frequency of 4 indicates that a given edge was detected by all 4 methods in a single dataset. The code can be easily modified to detect edges which are conserved across different studies and different methods. I will now try to estimate the taxonomic assortativity. To do this, the odds ratio (OR) for copresence links (given that the nodes belong to the same family) and mutual exlusion links (given that the nodes belong to different families) is calculated and the significance is evaluated using epi.2by2() function.

# same, by method and assotype, only for FMBN
# only meaningful for high number of studies
edge_freq_bymethod <- edge_list_df %>% 
  dplyr::filter(str_detect(dataset, "FMBN")) %>%
  group_by(method, asso_type) %>% 
  count(edge_name) %>%
  arrange(method, asso_type, -n)

# The following section evaluates taxonomic assortativity (i.e. evaluates if 
# copresence associations are more frequent among members of the same 
# family, order or class). The odds ratio of copresence relationships within
# the same family is calculated using epiR::epi.2by2()

# first, add taxonomy to the 
unique_tax <- node_stats_df %>% 
  dplyr::select(label, domain:species) %>%
  distinct()

edge_list_df_wtaxa <- edge_list_df
edge_list_df_wtaxa <- left_join(edge_list_df_wtaxa, 
                                dplyr::select(unique_tax, label, class, order, family), 
                                by = c("from_name" = "label")) %>%
  dplyr::rename(from_class = class, from_order = order, from_family = family)
edge_list_df_wtaxa <- left_join(edge_list_df_wtaxa, 
                                dplyr::select(unique_tax, label, class, order, family), 
                                by = c("to_name" = "label")) %>%
  dplyr::rename(to_class = class, to_order = order, to_family = family)
# check if same family or same class
edge_list_df_wtaxa <- edge_list_df_wtaxa %>%
  mutate(same_class = if_else(from_class == to_class, T, F),
         same_order = if_else(from_order == to_order, T, F),
         same_family = if_else(from_family == to_family, T, F)
         )

# use a loop to calculate odds ratios and probabilities
# get the number of studies

edge_list_df_wtaxa <- edge_list_df_wtaxa %>% 
  separate(dataset, into = c("study", "col2", "col3"), remove = F) %>%
  select(-col2, -col3) %>%
  mutate(study = as.factor(study),
         sf = factor(same_family, levels = c("TRUE", "FALSE")),
         so = factor(same_order, levels = c("TRUE", "FALSE")),
         sc = factor(same_class, levels = c("TRUE", "FALSE"))
  )

assort_results_list <- vector(mode = "list", length = nlevels(edge_list_df_wtaxa$study))
taxo_level_selection <- "family"
for(i in seq_along(levels(edge_list_df_wtaxa$study))){
  input_df_study <- edge_list_df_wtaxa %>% dplyr::filter(study == levels(study)[i]) %>%
    mutate(method = as.factor(method))
  inner_result_list <- vector(mode = "list", length = nlevels(input_df_study$method))
  for(j in seq_along(levels(input_df_study$method))){
   input_df_method <- input_df_study %>% 
      dplyr::filter(method == levels(method)[j])
    if(verbose_output) cat("\nProcessing", levels(edge_list_df_wtaxa$study)[i], 
                           "method", levels(input_df_method$method)[j],"\n")
    inner_result_list[[j]]<-try(odds_ratio(input_df_method, taxo_level = taxo_level_selection))
    if(verbose_output & class(inner_result_list[[j]]) == "data.frame") {
      cat("\nSuccess! Odds ratio estimated")
    } else {
      cat("\nFail: unable to return test results!")
    }
    names(inner_result_list)[j]<- levels(input_df_method$method)[j]
  }
  assort_results_list[[i]] <- inner_result_list
  names(assort_results_list)[i] <- levels(edge_list_df_wtaxa$study)[i]
}

Processing ST106 method ccrepe 

Success! Odds ratio estimated
Processing ST106 method sparcc 
NaNs producedNaNs produced

Success! Odds ratio estimated
Processing ST106 method spieceasi 
Error in if (any(x2)) upp[x2] <- rep(1, sum(x2)) : 
  missing value where TRUE/FALSE needed

Fail: unable to return test results!
Processing ST110 method ccrepe 

Success! Odds ratio estimated
Processing ST110 method sparcc 

Success! Odds ratio estimated
Processing ST110 method spieceasi 
NaNs producedError in uniroot(function(or) { : 
  f() values at end points not of opposite sign

Fail: unable to return test results!
Processing ST110 method spring 
NaNs producedNaNs produced

Success! Odds ratio estimated
Processing ST115 method ccrepe 

Success! Odds ratio estimated
Processing ST115 method sparcc 
Error in if (sum(A) > 0 & sum(B) > 0 & sum(C) > 0 & sum(D) > 0) { : 
  missing value where TRUE/FALSE needed

Fail: unable to return test results!
Processing ST115 method spieceasi 
Error in if (any(x2)) upp[x2] <- rep(1, sum(x2)) : 
  missing value where TRUE/FALSE needed

Fail: unable to return test results!
Processing ST131 method ccrepe 
NaNs producedError in uniroot(function(or) { : 
  f() values at end points not of opposite sign

Fail: unable to return test results!
Processing ST131 method sparcc 
Error in if (sum(A) > 0 & sum(B) > 0 & sum(C) > 0 & sum(D) > 0) { : 
  missing value where TRUE/FALSE needed

Fail: unable to return test results!
Processing ST136 method ccrepe 

Success! Odds ratio estimated
Processing ST136 method sparcc 
NaNs producedNaNs producedNaNs producedNaNs produced

Success! Odds ratio estimated
Processing ST136 method spieceasi 
NaNs producedError in uniroot(function(or) { : 
  f() values at end points not of opposite sign

Fail: unable to return test results!
Processing ST136 method spring 
NaNs producedNaNs produced

Success! Odds ratio estimated
# clean-up the list and put together the data frames

df_list <- vector(mode = "list", length = length(assort_results_list))
for(i in seq_along(assort_results_list)){
  df_list[[i]] <- df_return(assort_results_list[[i]]) 
  names(df_list)[i]<-names(assort_results_list)[i]
}

assort_results_df <- bind_rows(df_list, .id = "study")

# create dummy ORest values by replacing Inf
assort_results_df <- assort_results_df %>%
  mutate(OR_est_wdummy = if_else(OR_est==Inf, 99, OR_est)) %>%
  mutate(lOR_est_wdummy = log(OR_est_wdummy),
         significant = if_else(OR_p.value.1s <=0.05, T, F)) 

# plot, studies are pooled
ggplot(assort_results_df, mapping = aes(x = assort_test, y = lOR_est_wdummy, colour = significant)) +
  facet_wrap(~method) +
  geom_jitter(width = 0.2) +
  labs(y = "log(odds ratio)") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


# tabulating
xtabs(~ method + significant +assort_test , data = assort_results_df)
, , assort_test = cop_samefamily

        significant
method   FALSE TRUE
  ccrepe     4    0
  sparcc     2    1
  spring     1    1

, , assort_test = mutE_difffamily

        significant
method   FALSE TRUE
  ccrepe     4    0
  sparcc     2    1
  spring     1    1
rm(assort_results_list, input_df_study, inner_result_list, input_df_method, i, j, df_list)

if(play_audio) beep(sound = 6)
if(keep_time) toc()
Edge analysis: 25.936 sec elapsed

Even if the odds ratios are very high in some cases (actually Inf, due to division by 0) they are not significant. My conclusion is that there is not sufficient support for the occurrence of taxonomic assortativity (i.e. for a significantly higher probability of having a copresence link between members of the same family).
Similar results can be obtained when looking at the order and class level.

Citations and copyright.

Citations.

Citations for the metataxonomic studies used in this analysis are in the study_metadata data frame (if any). The main package used in this analysis is NetCoMi, and of course, Phyloseq. Citations for all packages are below.

LS0tCnRpdGxlOiAiSW5mZXJlbmNlIGFuZCBjb21wYXJpc29uIG9mIG1pY3JvYmlhbCBhc3NvY2lhdGlvbiBuZXR3b3JrcyB2MS4zIgpzdWJ0aXRsZTogIkluZmVycmluZyBuZXR3b3JrcyBmcm9tIEZNQk4gZGF0YSwgZ2VudXMgbGV2ZWw6IGV4YW1wbGUgd29ya2Zsb3ciCmF1dGhvcjogIkUuIFBhcmVudGUsIFNBRkUiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgojIEluc3RhbGwvbG9hZCBwYWNrYWdlcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi5iaW9jX3BhY2thZ2VzIDwtIGMoIkJpb2NNYW5hZ2VyIiwgImdlbmVmaWx0ZXIiLCAicGh5bG9zZXEiKQouY3Jhbl9wYWNrYWdlcyA8LSBjKCJkZXZ0b29scyIsICJwYXJhbGxlbCIsICJ0aWR5dmVyc2UiLCAiaWdyYXBoIiwgCiAgICAgICAgICAgICAgICAgICAgIlZlbm5EaWFncmFtIiwgInRpZHlncmFwaCIsICJsb2JzdHIiLCAidGljdG9jIiwgImJlZXByIiwKICAgICAgICAgICAgICAgICAgICAicHN5Y2giLCAiR0dhbGx5IiwgImdncmVwZWwiLCAiZ2dyYXBoIiwgImVnZyIsICJlcGlSIikKLmdpdGh1Yl9wYWNrYWdlcyA8LSBjKCJTcGllY0Vhc2kiLCJOZXRDb01pIikKIyBsb2JzdHIgaXMgY2FsbGVkIGZvciBsb2JzdHI6Om9ial9zaXplKCkKIyBlZ2cgaXMgb25seSB1c2VkIHRvIGFycmFuZ2UgcGxvdHMgaW4gZ3JpZHMKCi5pbnN0IDwtIC5iaW9jX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKaWYoYW55KCEuaW5zdCkpIHsKICBpZighLmluc3RbMV0pIHsKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICAgIC5pbnN0IDwtIC5iaW9jX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKICAgIH0KICBpZihhbnkoIS5pbnN0WzI6bGVuZ3RoKC5pbnN0KV0pKSB7CiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbCguYmlvY19wYWNrYWdlc1shLmluc3RdLCBhc2sgPSBGKQogICAgfQp9CgouaW5zdCA8LSAuY3Jhbl9wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpCmlmKGFueSghLmluc3QpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcyguY3Jhbl9wYWNrYWdlc1shLmluc3RdKQp9CgouaW5zdCA8LSAuZ2l0aHViX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKCiMgY2FyZWZ1bCBub3csIGJlY2F1c2UgdGhlIGluc3RhbGxhdGlvbiBvZiBOZXRDb01pIGNhbiBiZSBidWdneQojIGl0IHVzdWFsbHkgZmFpbHMgYmVjYXVzZSBpZiBmYWlscyB0byBpbnN0YWxsIG9uZSBvZiB0aGUgbWlzc2luZyBkZXBlbmRlbmNpZXMsCiMgaWYgYW55LiBJZiB0aGlzIGhhcHBlbnMsIGluc3RhbGwgdGhlIGRlcGVuZGVuY2llcyBmaXJzdC4uLgoKaWYoYW55KCEuaW5zdCkpewogIHJlcXVpcmUoZGV2dG9vbHMpCiAgaWYoIS5pbnN0WzFdKSBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInpkazEyMy9TcGllY0Vhc2kiKQogIGlmKCEuaW5zdFsyXSkgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJzdGVmcGVzY2hlbC9OZXRDb01pIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwZW5kZW5jaWVzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBvcyA9IGMoImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZy8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCaW9jTWFuYWdlcjo6cmVwb3NpdG9yaWVzKCkpKQp9CgojIExvYWQgcGFja2FnZXMgaW50byBzZXNzaW9uLCBhbmQgcHJpbnQgcGFja2FnZSB2ZXJzaW9uCnNhcHBseShjKC5jcmFuX3BhY2thZ2VzLCAuYmlvY19wYWNrYWdlcywgLmdpdGh1Yl9wYWNrYWdlcyksIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKCiMgb3RoZXIgc2V0dXAgb3BlcmF0aW9ucwpvcGFyIDwtIHBhcihuby5yZWFkb25seT1UUlVFKSAKcGFyKGFzaz1GKSAKIyBzZXQuc2VlZCgxMjM0KSAKIyBwbGF5IGF1ZGlvIG5vdGlmaWNhdGlvbnMKcGxheV9hdWRpbyA8LSBUCiMgdGltZSBpbXBvcnRhbnQgc3RlcHMKa2VlcF90aW1lIDwtIFQKIyB2ZXJib3NlIG91dHB1dDogd2lsbCBwcmludCBhZGRpdGlvbmFsIG9iamVjdHMgYW5kIG1lc3NhZ2VzCnZlcmJvc2Vfb3V0cHV0IDwtIFQKCiMgbG9hZCB0aGUgZnVuY3Rpb25zIGFuZCByZXR1cm4gYSByZXBvcnQKc291cmNlKGZpbGUucGF0aCgic291cmNlIiwiTUFOX2Z1bmN0aW9ucy5SIikpCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KSAjIG5vdGlmaWNhdGlvbgpgYGAKCiMgSW50cm9kdWN0aW9uLiAgCgpUaGlzIG5vdGVib29rIHJlcG9ydHMgb24gdGhlIGluZmVyZW5jZSBvZiBtaWNyb2JpYWwgYXNzb2NpYXRpb24gbmV0d29ya3MgZnJvbSBtZXRhLXRheG9ub21pYyBkYXRhIGV4dHJhY3RlZCBmcm9tIFtEYWlyeUZNQk5dKGh0dHA6Ly9keC5kb2kub3JnLzEwLjE3NjMyLzNjd2Y3MjlwMzQuMykgdXNpbmcgcGFja2FnZSBbTmV0Q29NaV0oaHR0cHM6Ly9naXRodWIuY29tL3N0ZWZwZXNjaGVsL05ldENvTWkpLCBhZnRlciBzb21lIHByZS1wcm9jZXNzaW5nIGNhcnJpZWQgb3V0IG1vc3RseSB3aXRoIHBhY2thZ2UgW3BoeWxvc2VxXShodHRwczovL2pvZXk3MTEuZ2l0aHViLmlvL3BoeWxvc2VxLykuICAKClRoZSBvYmplY3RpdmUgb2YgdGhpcyBhbmFseXNpcyBpczogIAoKKiBpbmZlciBtaWNyb2JpYWwgYXNzb2NpYXRpb24gbmV0d29ya3MgKGF0IHRoZSBnZW51cyBsZXZlbCkgZm9yIHNlbGVjdGVkIGNoZWVzZSBzdHVkaWVzIHN0b3JlZCBpbiBEYWlyeUZNQk4gdXNpZ24gZGlmZmVyZW50IGluZmVyZW5jZSBtZXRob2RzICAKCiogaWRlbnRpZnkgYXNzb2NpYXRpb25zIHdoaWNoIGFyZSBjb25zZXJ2ZWQgYWNyb3NzIG1ldGhvZHMgIAoKKiBjb21wYXJlIGRpZmZlcmVudCBpbmZlcmVuY2UgbWV0aG9kcyAKCiogaWRlbnRpZnkgYW5kIGNvbXBhcmUgaHVicyBpbiBkaWZmZXJlbnQgbmV0d29ya3MKICAKVGhlIGRhdGEgdXNlZCBpbiB0aGlzIHNjcmlwdCBpbmNsdWRlIHN0dWRpZXMgZnJvbSBGTUJOIHZlcnNpb25zIDMuMiwgd2l0aCBkaWZmZXJlbnQgcGxhdGZvcm1zIGFuZCB0YXJnZXQsIGJ1dCBhbmFseXplZCB1c2luZyB0aGUgc2FtZSBwaXBlbGluZSBiYXNlZCBvbiBEQURBMi4gVGhlIHNjcmlwdCBpcyBkZXNpZ25lZCB0byB3b3JrIHdpdGggcGh5bG9zZXEgb2JqZWN0cyBleHRyYWN0ZWQgZnJvbSBGb29kTWljcm9iaW9uZXQgb3IgRGFpcnlGTUJOIHVzaW5nIHRoZSBTaGlueUZNQk4gYXBwIERhaXJ5Rk1CTiBjYW4gYmUgZG93bmxvYWRlZCBmcm9tIFtNZW5kZWxleSBkYXRhXShodHRwczovL2RhdGEubWVuZGVsZXkuY29tL2RhdGFzZXRzLzNjd2Y3MjlwMzQvMyk6IG5ldyB2ZXJzaW9ucyBhcmUgZHVlIHNvb24uICAKVGhlIHNjcmlwdCB3aWxsIGFsc28gd29yayBjb3JyZWN0bHkgd2l0aCBwaHlsb3NlcSBvYmplY3RzIGdlbmVyYXRlZCB1c2luZyB0aGUgREFEQTIgW0Jpb0NvbmR1Y3RvciBwaXBlbGluZV0oaHR0cHM6Ly9iZW5qam5lYi5naXRodWIuaW8vZGFkYTIvdHV0b3JpYWwuaHRtbCkgYnV0IHRoZSB1c2Ugb2YgU0lMVkEgYXMgYSB0YXhvbm9taWMgZGF0YWJhc2UgaXMgYWR2aXNhYmxlLiAgV2l0aCBzb21lIGFkYXB0YXRpb25zIHRoZSBzY3JpcHQgbWF5IHdvcmsgd2l0aCBwaHlsb3NlcSBvYmplY3RzIGdlbmVyYXRlZCB1c2luZyBvdGhlciBwaXBlbGluZXMuICAKCiMgQW5hbHlzaXMgd29ya2Zsb3cuICAKCjEuICoqc3RhcnQgZnJvbSBuIChuPjEpIGBwaHlsb3NlcWAgb2JqZWN0cyoqLiBJbiB0aGlzIGFuYWx5c2lzIEkgd2lsbCB1c2UgcGh5bG9zZXEgb2JqZWN0cyBleHRyYWN0ZWQgZnJvbSBEYWlyeUZNQk4gKHdoaWNoLCBpbiB0dXJuLCBpbmNsdWRlcyBhbGwgc3R1ZGllcyBvbiBkYWlyeSBwcm9kdWN0cyBzdG9yZWQgaW4gRm9vZE1pY3JvYmlvbmV0KS4gQWx0aG91Z2ggcG9vbGluZyBhdCB0aGUgZ2VudXMgbGV2ZWwgaXMgbGlrZWx5IHRvIG11ZGRsZSBzb21lIGludGVyYWN0aW9ucywgaXQgaXQgdGhlIG9ubHkgcmVhc29uYWJsZSBjaG9pY2Ugd2hlbiBjb21wYXJpbmcgc3R1ZGllcyB3aXRoIHZhc3RseSBkaWZmZXJlbnQgdGFyZ2V0cywgc2VxdWVuY2luZyBwbGF0Zm9ybXMsIGJpb2luZm9ybWF0aWMgcGlsZWxpbmVzLCBldGMuIAoKMi4gKipwZXJmb3JtIGRhdGEtd3JhbmdsaW5nIHdpdGggcGh5bG9zZXEgZnVuY3Rpb25zIChtb3N0bHkpKiouIFRoZXJlIGFyZSBhIG51bWJlciBvZiBiYXNpYyBvcGVyYXRpb25zIHdoaWNoIHNob3VsZCBiZSBwZXJmb3JtZWQ6ICAKCiAgICArIHJlbW92ZSBzYW1wbGVzIHdpdGggYSBsb3cgbnVtYmVyIG9mIHNlcXVlbmNlcyAoYG1pbnNlcWApIHVzaW5nIHRoZSBwcnVuZV9zYW1wbGVzKCkgZnVuY3Rpb25zIChhbmQgcGVyaGFwcyBza2lwIHRoZSBhbmFseXNpcyBpZiB0aGVyZSBpcyBsZXNzIHRoYW4gbWluX3NhbXBsZXMpLiBOb3RlIHRoYXQgc2FtcGxlIGFuZCB0YXhvbm9taWMgZmlsdGVyaW5nIGNhbiBiZSBhbHNvIHBlcmZvcm1lZCB3aXRoaW4gdGhlIGBOZXRDb01pOjpuZXRDb25zdHJ1Y3QoKWAgZnVuY3Rpb24KICAKICAgICsgcmVtb3ZlIF9FdWthcnlvdGFfIGFuZCBfQ2hsb3JvcGxhc3RzXyB1c2luZyBzdWJzZXRfdGF4YSgpCiAgCiAgICArIHJlbW92ZSB0YXhhIHdpdGggYW1iaWd1b3VzIGlkZW50aWZpY2F0aW9uIG9yIHBvb3IgaWRlbnRpZmljYXRpb24gKHNheSBhdCB0aGUgZG9tYWluIG9yIHBoeWx1bSBsZXZlbCkKICAKICAgICsgcGVyZm9ybSBwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UgZmlsdGVyaW5nIChwcmV2YWxlbmNlIGFib3ZlIG1pbl9wcmV2IEFORCBhYnVuZGFuY2UgYWJvdmUgbWluX3JlbF9hYiksIHBvc3NpYmx5IHVzaW5nIGBmaWx0ZXJfdGF4YSgpYAogIAogICAgKyByZXR1cm4gc29tZSBzb3J0IG9mIHN1bW1hcnkgZm9yIHdoYXQgaGFzIGJlZW4gZG9uZSBhbmQgdGhlIGVmZmVjdCBvbiB0aGUgb3JpZ2luYWwgdGFibGUgKGluIHRlcm1zIG9mIGxvc3Mgb2YgdGF4YSwgc2VxdWVuY2VzLCBzYW1wbGVzKSwgaW5jbHVkaW5nIHNvbWUgbWVhc3VyZSBvZiBkaXZlcnNpdHkgYW5kIGV2ZW5uZXNzICAKICAKMy4gKipwZXJmb3JtIG1pY3JvYmlhbCBhc3NvY2lhdGlvbiBuZXR3b3JrIGluZmVyZW5jZSB3aXRoIGBuZXRDb25zdHJ1Y3QoKWAqKiB3aXRoIHNvbWUgZXJyb3IgdHJhcHBpbmcgYW5kIHN0b3JlIHJlc3VsdHMgaW4gYSBsaXN0LiBUaGlzIGV4YW1wbGUgdXNlcyB0aGUgZm9sbG93aW5nIGluZmVyZW5jZSBtZXRob2RzOiAgCgogICAgKyAqKlNwYXJDQyoqIChzcGFyY2MoKSBpbiBTcGllY0Vhc2kgcGFja2FnZSk6IG5lZWRzIGhpZ2ggbnVtYmVyIG9mIHRheGEgYW5kIHNwYXJzZSBtYXRyaXggIAogICAgCiAgICArICoqQ0NSRVBFKiogKGNjcmVwZSBwYWNrYWdlLCBpdCBpcyB0aGUgbWFpbiBhcHByb2FjaCB1c2VkIGluIENvTmV0KSAgCiAgICAKICAgICsgKipTcGllY0Vhc2kqKiAoU3BpZWNFYXNpIHBhY2thZ2UpICAKICAgIAogICAgKyAqKlNQUklORyoqIChTUFJJTkcgcGFja2FnZSkgIAoKNC4gKipnZXQgbm9kZXMsIGVkZ2VzIGFuZCBuZXR3b3JrIHN0YXRzKio6IHVzZSBOZXRDb01pOjpuZXRBbmFseXplKCkgdG8gZ2V0IGFuIG9iamVjdCB3aXRoIGJvdGggbm9kZSBhbmQgbmV0d29yayBzdGF0czsgcHJpbnQgYSBzdW1tYXJ5IGZvciBuZXR3b3JrIHN0YXRzOyBidWlsZCBhIHRpZHlncmFwaCBvYmplY3QgZm9yIGZ1cnRoZXIgc3RhdHM7IGJ1aWxkIHRpZHkgZGF0YSBmcmFtZXMgd2l0aCByZWxldmFudCBzdGF0czsgCgpUaGUgc2NyaXB0IGlzIGVzaWduZWQgdG8gc2F2ZSBkYXRhIGFuZCBhbW55IG9mIHRoZSBwbG90cyBpbiBhIHN1YmZvbGRlciAoaGVyZSAibmV0Y29tcGFyZV9vdXRwdXQiKS4gIAoKIyBUaGUgYW5hbHlzaXMuCgojIyBTZXR0aW5nIHRoZSBvcHRpb25zLiAgCgpBIG51bWJlciBvZiBvcHRpb25zIHBlcnRhaW5pbmcgdG8gdGhlIGFuYWx5c2lzIG9yIHJsYXRlZCB0byBzYXZlIG9wZXJhdGlvbnMgY2FuIGJlIHNldCBpbiB0aGUgY2h1bmsgYmVsb3cgYW5kIHdpbGwgYmUgc2F2ZWQgYW5kIHByaW50ZWQuIAogIApgYGB7ciBzZXR0aW5nX29wdGlvbnMsIGVjaG89RkFMU0V9CgojIyMgcHJvY2Vzc2luZyBhbmQgZm9sZGVycyAjIyMKIyB0aGUgZm9sbG93aW5nIGNvbW1hbmQgZGV0ZWN0cyB0aGUgbnVtYmVyIG9mIGNvcmVzIG9uIFVOSVgvTWFjT1MKbmNvcmVzIDwtIHBhcmFsbGVsOjpkZXRlY3RDb3Jlcyhsb2dpY2FsID0gRikgIyB0byBkZXRlY3QgcGh5c2ljYWwgY29yZXMgaW4gTWFjT1MKIyB0aGUgbmFtZSBvZiB0aGUgZm9sZGVyIGNvbnRhaW5pbmcgdGhlIHBoeWxvc2VxIG9iamVjdHMKZGF0YV9mb2xkZXIgPC0gImlucHV0X2RhdGEiCiMgdGhlIG5hbWUgb2YgdGhlIGZvbGRlciBjb250YWluaW5nIGFkZGl0aW9uYWwgZnVuY3Rpb25zICsgb3B0aW9uYWwgZGF0YSAobGlrZSB0aGUgbG9va3VwIHRhYmxlKQpzb3VyY2VfZm9sZGVyIDwtICJzb3VyY2UiCiMgdGhlIG5hbWUgb2YgdGhlIG91dHB1dCBmb2xkZXIKb3V0cHV0X2ZvbGRlciA8LSAibmV0Y29tcGFyZV9vdXRwdXQiCnByb2Nlc3NfYmF0Y2ggPC0gVAojIGlmIHRydWUsIGFsbCBwaHlsb3NlcXMgaW4gdGhlIHN1YmZvbGRlciBwaHlzZXFvYmogd2lsbCBiZSBwcm9jZXNzZWQgaW4gYmF0Y2ggKG1heSB0YWtlIHRpbWUpCgojIHNvbWUgZ3JhcGggb3B0aW9ucywgc2hhcmVkIGV2ZXJ5d2hlcmUKZHBpX29wdGlvbiA8LSAzMDAKZ190eXBlIDwtICJ0aWZmIiAKCiMgdXNlIGEgbG9va3VwIHRhYmxlIHRvIGZpeCB0aGUgZ2VuZXJhIGFuZCBmYW1pbGllcyBmb3IgTGFjdG9iYWNpbGxhbGVzCnVzZV9sb29rdXAgPC0gVAppZih1c2VfbG9va3VwKXsKICBsb29rdXBfdGFibGUgPC0gcmVhZF90c3YoZmlsZS5wYXRoKCJzb3VyY2UiLCAibG9va3VwX2xhY3RvYmFjaWxsdXMudHh0IiksIGNvbF90eXBlcyA9ICJjY2NjY2MiKQp9CgpvdXRfZmlsZW5hbWVfcHJlZiA8LSAiTUFOX0ZNQk5fZ2VudXNfMyIgIyB0aGUgcHJlZml4IHdoaWNoIHdpbGwgYmUgdXNlZCBmb3IgYWxsIG91dHB1dCBmaWxlbmFtZXMKCiMjIyBmaWx0ZXJpbmcgb3B0aW9ucyAjIyMKbWluX3NhbXBsZXMgPC0gMjAKIyB0aGUgbWluaW11bSBudW1iZXIgb2Ygc2FtcGxlcyBpbiB0aGUgZGF0YSBzZXQ6IHN0dWRpZXMgd2l0aCA8IG1pbl9zYW1wbGVzIGFmdGVyIGZpbHRlcmluZyB3aWxsIGJlIGV4Y2x1ZGVkIGZyb20gdGhlIGFuYWx5c2lzCm1pbl9zZXFzIDwtIDEwMDAKIyB0aGUgbWluaW11bSBudW1iZXIgb2Ygc2VxdWVuY2VzIHBlciBzYW1wbGUKCiMgcmVtb3ZlcyB1bmNoYXJhY3Rlcml6ZWQgdGF4YSAoaS5lLiB0YXhhIG1pc3NpbmcgdGhlIGlkZW50aWZpY2F0aW9uIGF0IHRoZSBwaHlsdW0gbGV2ZWwgb3IgYWJvdmUpCnJtX3VuY2hhciA8LSBUCiMgcmVtb3ZlIEV1a2FyeW90YQpybV9ldWsgPC0gVAojIHJlbW92ZSBjaGxvcm9waGFsc3RzIGFuZCBtaXRvY2hvbmRyaWEKcm1fY2hsbWl0IDwtIFQKIyB0YXhvbm9taWMgYWdnbG9tZXJhdGlvbiBvcHRpb25zOiAibm9uZSIgbWVhbnMgbm8gYWdncmVnYXRpb247IGlmIHRoZSBpbnB1dCBkYXRhIGFyZSBBU1ZzIG9yIE9UVXMgdGhleSB3aWxsIGJlIHVzZWQgYXMgc3VjaAojIGlmIHRoZSBpbnB1dCBmaWxlIGlzIGZyb20gRk1CTiBkYXRhIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0cyBhcmUgYWdncmVnYXRlZCBhdCBkaWZmZXJlbnQgdGF4b25vbWljIGxldmVscy4KIyB3aGVuIGNvbXBhcmluZyBkYXRhc2V0cyBvYnRhaW5lZCB1c2luZyBkaWZmZXJlbnQgZ2VuZSB0YXJnZXRzIG9yIHBpcGVsaW5lcyBzaG91bGQgYmUgc2V0IHRvICJnZW51cyIKIyAibm9uZSIsICJnZW51cyIsIG9yICJmYW1pbHkiKTsgZG8gbm90IHVzZSBzcGVjaWVzLCB3aWxsIG1vc3QgbGlrZWx5IGNhdXNlIGVycm9ycwp0YXhnbG9tIDwtICJnZW51cyIgIyB0YXggZ2xvbSB3aWxsIGJlIHBlcmZvcm1lZCBhdCB0aGUgZ2VudXMgbGV2ZWwKIyByZW1vdmUgdGF4YSB3aGljaCBhcmUgdW5jaGFyYWN0ZXJpemVkIGF0IHRoZSBmYW1pbHksIG9yZGVyIGFuZCBjbGFzcyBsZXZlbCAoc2hvdWxkIG5vdCBiZSB2ZXJ5IG1hbnkgaW4gbW9zdCBkYXRhc2V0cykKYWJvdmVfZ2VudXNfZmxhZyA8LSBUCiMgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcgojIGZsYWcgdG8gZGVjaWRlIGlmIE9UVSBzaG91bGQgYmUgZmlsdGVyZWQsIGkuZS4gaWYgdGhlIGZpbHRlciBpcyB0byBiZSBhcHBsaWVkIGF0IGFsbApmaWx0ZXJPVFVzIDwtIFRSVUUKIyBmbGFnIHRvIHNldCB0aGUgYXBwbGljYXRpb24gb2YgYSBwcmV2YWxlbmNlIGZpbHRlcgpwcmV2X2ZpbHRlciA8LSBUUlVFCiMgdGhlIHRocmVzaG9sZCBvZiB0aGUgZmlsdGVyLCBhcyBmcmFjdGlvbiBvZiBzYW1wbGVzCnByZXZfdGhyZXNob2xkIDwtIDAuMDUKIyB0aGUgYWJ1bmRhbmNlIGFzIGZyYWN0aW9uIG9mIHNlcXVlbmNlcyAoaW4gYXQgbGVhc3Qgb25lIHNhbXBsZSkKYWJfdGhyZXNob2xkIDwtIDAuMDA1CnBhc3NfYm90aCA8LSBUUlVFIAojIGlmIHRydWUgYW4gT1RVIG11c3QgcGFzcyBib3RoIGFidW5kYW5jZSBhbmQgcHJldmFsZW5jZSBmaWx0ZXIsIG90aGVyd2lzZQojIHBhc3NpbmcgZWl0aGVyIGlzIGVub3VnaAojIG9wdGlvbnMgZm9yIHNhdmluZyBvdXRwdXQgb2YgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcgpzYXZlX3ByZXZfYWJfcGxvdCA8LSBUCiMgZmxhZyBmb3Igc2F2aW5nIHRoZSBwcmV2IGFiIHBsb3QKcHJpbnRfcHJldl9hYl9wbG90IDwtIEYKIyBmbGFnIGZvciBwcmludGluZyB0aGUgcGxvdApzYXZlX3ByZXZfdGFibGUgPC1UIAojIGZsYWcgZm9yIHNhdmluZyB0aGUgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIHRhYmxlOyBzaG91bGQgYmUgc2F2ZWQgYmVjYXVzZSBpdCBjYW4gYmUgdXNlZCBsYXRlcgojIHRvIGFkZCBpbmZvIHRvIHRoZSBub2RlIHN0YXRzCnNhdmVfcHJldl9hYl9saXN0IDwtVAoKIyBhIHZlY3RvciB3aXRoIHRoZSBtZXRob2RzIHVzZWQgZm9yIGluZmVyZW5jZSwgdXNlIHRoZSBzcGVsbGluZyB1c2VkIGluIGBuZXRDb25zdHJ1Y3QoKWAKIyBzZWVOZXRDb01pIGRvY3VtZW50YXRpb24gZm9yIGEgbHNpdCBvZiBhdmFpbGFibGUgbWV0aG9kcyBhbmQgb3B0aW9uczsKIyB0aGUgZmlyc3QgbWV0aG9kIGlzIHRoZSBvbmUgd2hpY2ggd2lsbCBiZSB1c2VkIHRvIGRvIGNvbXBhcmlzb25zIHdpdGhpbiBzYW1wbGUKIyBpZiB5b3Ugd2FudCB0byBydW4ganVzdCBvbmUgbGlzdCBvbmx5IHRoYXQKCmluZl9tZXRob2RzIDwtIGMoInNwaWVjZWFzaSIsICJzcHJpbmciLCAic3BhcmNjIiwgImNjcmVwZSIpCgojIHRoZSBsaXN0IG9mIHBhcmFtZXRlcnMgdG8gYmUgcGFzc2VkIGluIGBuZXRDb25zdHJ1Y3QoKWAgZm9yIGVhY2ggb2YgdGhlIG1ldGhvZHM7CiMgdGhlcmUgbXVzdCBiZSBvbmUgZW50cnkgZm9yIGVhY2ggb2YgaGUgcGFyYW1ldGVycyBpbiB0aGUgbGlzdAojIHNvbWUgcGFyYW1ldGVycyBhcmUgbm90IHJlYWxseSBuZWVkZWQgYW5kIHdpbGwgYmUgaWdub3JlZAppbmZfbWV0aG9kc19wYXJhbSA8LSBsaXN0KAogIHNwaWVhY2Vhc2kgPSBsaXN0KAogICAgc3Bhck1ldGhvZCA9ICJ0LXRlc3QiLAogICAgYWxwaGEgPSAwLjAwMSwKICAgIG1lYXN1cmVQYXJMaXN0ID0gbGlzdCgKICAgICAgbWV0aG9kID0gJ21iJywKICAgICAgbGFtYmRhLm1pbi5yYXRpbyA9IDFlLTIsCiAgICAgIG5sYW1iZGEgPSAyMCwKICAgICAgcHVsc2FyLnBhcmFtcyA9IGxpc3QocmVwLm51bSA9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNTApCiAgICApLAogICAgbm9ybW1ldGhvZFBhciA9ICJub25lIiwKICAgIHplcm9tZXRob2RQYXIgPSAibm9uZSIsCiAgICBkaXNzRnVuY1BhciA9ICJzaWduZWQiLAogICAgdmVyYm9zZVBhciA9IDEKICApLAogIHNwcmluZyA9IGxpc3QoCiAgICBzcGFyTWV0aG9kID0gInQtdGVzdCIsCiAgICBhbHBoYSA9IDAuMDAxLAogICAgbWVhc3VyZVBhckxpc3QgPSBsaXN0KG5sYW1iZGEgPSA1MCwgcmVwLm51bSA9IDUwKSwKICAgICAgICBub3JtbWV0aG9kUGFyID0gIm5vbmUiLAogICAgemVyb21ldGhvZFBhciA9ICJub25lIiwKICAgIGRpc3NGdW5jUGFyID0gInNpZ25lZCIsCiAgICB2ZXJib3NlUGFyID0gMQogICksCiAgc3BhcmNjID0gbGlzdCgKICAgIHNwYXJNZXRob2QgPSAidC10ZXN0IiwKICAgIGFscGhhID0gMC4wMDEsCiAgICBtZWFzdXJlUGFyTGlzdCA9IGxpc3QoaXRlciA9IDEwMCwgaW5uZXJfaXQgPSAyMCwgdGggPSAwLjA1KSwKICAgIG5vcm1tZXRob2RQYXIgPSAibm9uZSIsCiAgICB6ZXJvbWV0aG9kUGFyID0gIm5vbmUiLAogICAgZGlzc0Z1bmNQYXIgPSAic2lnbmVkIiwKICAgIHZlcmJvc2VQYXIgPSAxCiAgKSwKICBjY3JlcGUgPSBsaXN0KAogICAgc3Bhck1ldGhvZCA9ICJ0LXRlc3QiLAogICAgYWxwaGEgPSAwLjAwMSwKICAgIG1lYXN1cmVQYXJMaXN0ID0gTlVMTCwKICAgIG5vcm1tZXRob2RQYXIgPSAiZnJhY3Rpb25zIiwKICAgIHplcm9tZXRob2RQYXIgPSAibm9uZSIsCiAgICBkaXNzRnVuY1BhciA9ICJzaWduZWQiLAogICAgdmVyYm9zZVBhciA9IDEKICApCikKCiMgb3B0aW9ucyBmb3IgbmV0d29yayBhbmFseXNpcwojIGRvIFZlbm4gcGxvdApkb19WZW5uIDwtIFQKIyBjYWxjdWxhdGVzIGVkZ2UgYmV0d2Vlbm5lc3MKY2FsY19lX2JldHcgPC1UCiMgbWVyZ2Ugbm9kZSBzdGF0cwptZXJnZV9ub2RlX3N0YXRzIDwtIFQKCiMgYnVpbGQgYW5kIHNhdmUgYSBsaXN0IHdpdGggdGhlIG9wdGlvbnMsIGZvciByZXByb2R1Y2liaWxpdHkKb3B0aW9uX2xpc3QgPC0gbGlzdCgKICBnZW5lcmFsX29wdGlvbnMgPSBsaXN0KAogICAgbmNvcmVzID0gbmNvcmVzLAogICAgZGF0YV9mb2xkZXIgPSBkYXRhX2ZvbGRlciwKICAgIHNvdXJjZV9mb2xkZXIgPSBzb3VyY2VfZm9sZGVyLAogICAgb3V0cHV0X2ZvbGRlciA9IG91dHB1dF9mb2xkZXIsCiAgICBwcm9jZXNzX2JhdGNoID0gcHJvY2Vzc19iYXRjaCwKICAgIHVzZV9sb29rdXAgPSB1c2VfbG9va3VwLAogICAgb3V0X2ZpbGVuYW1lcyA9IG91dF9maWxlbmFtZV9wcmVmLAogICAgZ3JlcyA9IGRwaV9vcHRpb24sCiAgICBndHlwZSA9IGdfdHlwZQogICksCiAgbWV0aG9kc19vcHRpb25zID0gbGlzdCgKICAgIG1ldGhvZHMgPSBpbmZfbWV0aG9kcywKICAgIHBhcmFtZXRlcnMgPSBpbmZfbWV0aG9kc19wYXJhbQogICksCiAgZmlsdGVyaW5nX29wdGlvbnMgPSBsaXN0KAogICAgbWluX3NhbXBsZXMgPSBtaW5fc2FtcGxlcywKICAgIG1pbl9zZXFzID0gbWluX3NlcXMsCiAgICBnbG9tX3RheGEgPSB0YXhnbG9tLAogICAgcm11bmNoYXJfZHAgPSBybV91bmNoYXIsCiAgICBybWNobG1pdCA9IHJtX2NobG1pdCwKICAgIHJtZXVrID0gcm1fZXVrLAogICAgcm11bmNoYXJfY29mID0gYWJvdmVfZ2VudXNfZmxhZywKICAgIGZpbHRlcl9PVFVzID0gZmlsdGVyT1RVcywKICAgIHByZXZmaWx0ZXIgPSBwcmV2X2ZpbHRlciwKICAgIHByZXZ0aHJlc2hvbGQgPSBwcmV2X3RocmVzaG9sZCwKICAgIGFidGhyZXNob2xzID0gYWJfdGhyZXNob2xkLAogICAgcGFzc2JvdGggPSBwYXNzX2JvdGgsCiAgICBzYXZlcHJldmFicGxvdCA9IHNhdmVfcHJldl9hYl9wbG90LCAKICAgIHByaW50cHJldmFicGxvdCA9IHByaW50X3ByZXZfYWJfcGxvdCwKICAgIHNhdmVwcmV2YWJ0YWJsZSA9IHNhdmVfcHJldl90YWJsZSwKICAgIHNhdmVfcHJldl9hYl9saXN0ID0gc2F2ZV9wcmV2X2FiX2xpc3QKICApLAogIG5ldHN0YXRfb3B0aW9ucyA9IGxpc3QoCiAgICBkb1Zlbm4gPSBkb19WZW5uLAogICAgY2FsY0ViZXR3ID0gY2FsY19lX2JldHcsCiAgICBtZXJnZU5zdGF0cyA9IG1lcmdlX25vZGVfc3RhdHMKICApCikKCnNhdmUob3B0aW9uX2xpc3QsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0X2ZvbGRlciwgcGFzdGUob3V0X2ZpbGVuYW1lX3ByZWYsIl9vcHRpb25zLlJkYXRhIikpKQoKCmV4dF9mdW5jdGlvbnMgPC0gcHJpbnRfZnVuY3Rpb25fcmVwb3J0KCkKaWYodmVyYm9zZV9vdXRwdXQpIHsKICBwcmludChleHRfZnVuY3Rpb25zKQogIHByaW50KG9wdGlvbl9saXN0KQogIH0KCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KSAjIG5vdGlmaWNhdGlvbgoKCmBgYAojIyBMb2FkaW5nIHRoZSBvYmplY3RzIHRvIHByb2Nlc3MuICAKClRoZSBpbnB1dCBkYXRhIGFyZSBzdG9yZWQgaW4gYSBmb2xkZXIgKGlucHV0X2RhdGEpIGFuZCBwcm9jZXNzZWQgb25lIGJ5IG9uZSAoZGVwZW5kaW5nIG9uIHVzZXIgaW5wdXQpIG9yIGluIGJhdGNoIChzZWUgcHJvY2Vzc19iYXRjaCBvcHRpb24pLiBUaGUgZm9sZGVyIHNob3VsZCBhbHNvIGNvbnRhaW4gYSAudHN2IHdpdGggc3R1ZHkgbWV0YWRhdGEsIHdpdGggYSBsYWJlbCB2YXJpYWJsZSB3aXRoIHRoZSBudW1iZXIgb2YgdGhlIHN0dWR5IGFuZCBhbiBpbmRpY2F0b3Igb24gdGhlIG5hdHVyZSBvZiB0aGUgZmlsZSAoRk1CTiBvciwgaWYgdGhlIGFjY24gbnVtYmVyIGlzIGluIHRoZSBuYW1lLCBBU1YpLiBIZXJlIEkgYW0gbG9hZGluZyA1IHN0dWRpZXMgZnJvbSBEYWlyeUZNQk4gKFNUMTA2LCBTVDExMCwgU1QxMTUsIFNUMTMxLCBTVDEzNikuIFRoZSBzdHVkaWVzIGRpZmZlciBpbiB0aGUgdGFyZ2V0cyAoU1QxMDYgdGFyZ2V0cyBWMS1WMyBSTkEsIHRoZSBvdGhlcnMgdGhlIDE2UyBSTkEgZ2VuZSwgVjQgb3IgVjMtVjQpIGFuZCBwbGF0Zm9ybS4gTG9vayBhdCB0aGUgZXhhbXBsZSBmaWxlcyB0byBzZWUgaG93IHRoZSBzdHVkeV9tZXRhZGF0YSBmaWxlIHNob3VsZCBiZSBzZXR1cC4gICAKCmBgYHtyIGxvYWRfaW5wdXRfZGF0YSwgZWNobyA9IEZBTFNFfQoKaWYoa2VlcF90aW1lKSB0aWMoImxvYWRpbmcgcGh5bG9zZXEgb2JqZWN0cyIpCiMgb2J0YWluIHRoZSBsaXN0IG9mIGZpbGVzIGluIHRoZSBpbnB1dCBmb2xkZXIKaW5wdXRfZmlsZV9saXN0IDwtIGxpc3QuZmlsZXMoZmlsZS5wYXRoKGRhdGFfZm9sZGVyKSkKIyBtYW5hZ2UgLnJkYXRhIGZpbGVzIGZpcnN0IChidXQgdGhleSBtdXN0IGJlIHBoeWxvc2VxIG9iamVjdHMgb3Igd2lsbCBiZSBkZWxldGVkIGxhdGVyKQpyZGF0YV9pbnB1dF9maWxlX2xpc3QgPC0gaW5wdXRfZmlsZV9saXN0W3N0cl9kZXRlY3QoaW5wdXRfZmlsZV9saXN0LCAiLnJkYXRhfC5SZGF0YXwuUkRBVEEiKV0gCiMgZ2V0IHRoZSBsaXN0IC5SZGF0YQpyZGF0YV9uYW1lc19saXN0IDwtIHN0cl9yZW1vdmUocmRhdGFfaW5wdXRfZmlsZV9saXN0LCAiLnJkYXRhfC5SZGF0YXwuUkRBVEEiKQpyZGF0YV9wYXRoX2xpc3QgPC0gZmlsZS5wYXRoKGRhdGFfZm9sZGVyLHJkYXRhX2lucHV0X2ZpbGVfbGlzdCkKIyBsb2FkIGFuZCBzYXZlIGFzIC5yZHMsIHJlbW92ZSBvYmplY3RzIGFuZCBmaWxlcwpmb3IgKGkgaW4gc2VxX2Fsb25nKHJkYXRhX3BhdGhfbGlzdCkpewogIGxvYWQocmRhdGFfcGF0aF9saXN0W2ldKSAjIGxvYWRzIHRoZSBvYmplY3QgYXMgcGh5c2VxZGF0YQogIHNhdmVSRFMocGh5c2VxZGF0YSwgZmlsZS5wYXRoKGRhdGFfZm9sZGVyLCBwYXN0ZShyZGF0YV9uYW1lc19saXN0W2ldLCIucmRzIixzZXA9IiIpKSkKICBybShwaHlzZXFkYXRhKQogIGZpbGUucmVtb3ZlKHJkYXRhX3BhdGhfbGlzdFtpXSkKfQoKIyBnZXQgdGhlIG5hbWVzIGZvciB0aGUgLnJkcyBmaWxlcwoKaW5wdXRfZmlsZV9saXN0IDwtIGxpc3QuZmlsZXMoZmlsZS5wYXRoKGRhdGFfZm9sZGVyKSkKcmRzX2lucHV0X2ZpbGVfbGlzdCA8LSBpbnB1dF9maWxlX2xpc3Rbc3RyX2RldGVjdChpbnB1dF9maWxlX2xpc3QsICIucmRzfC5SRFMiKV0gCm5hbWVzX2xpc3QgPC0gc3RyX3JlbW92ZShyZHNfaW5wdXRfZmlsZV9saXN0LCAiLnJkc3wuUkRTIikKcmRzX3BhdGhfbGlzdCA8LSBmaWxlLnBhdGgoZGF0YV9mb2xkZXIscmRzX2lucHV0X2ZpbGVfbGlzdCkKIyBsb2FkIGFsbCB0aGUgZmlsZXMgaW4gYSBsaXN0IHVzaW5nIGEgZnVuY3Rpb25hbApwaHlzZXFfbGlzdCA8LSBsYXBwbHkocmRzX3BhdGhfbGlzdCwgcmVhZFJEUykKIyBjaGVjayBpZiBhbGwgYXJlIHBoeWxvc2VxIG9iamVjdHMKaXNfcGh5bG9zZXEgPC0gc2FwcGx5KHBoeXNlcV9saXN0LCBmdW5jdGlvbih4KSBjbGFzcyh4KT09InBoeWxvc2VxIikKIyBjbGVhbiB1cCB0aGUgbGlzdCBpZiBuZWNlc3NhcnkgYW5kIGdpdmUgbmFtZXMKcGh5c2VxX2xpc3QgPC0gcGh5c2VxX2xpc3RbaXNfcGh5bG9zZXFdCm5hbWVzKHBoeXNlcV9saXN0KSA8LSBuYW1lc19saXN0W2lzX3BoeWxvc2VxXQojIGlmIHByb2Nlc3NfYmF0Y2ggPT0gRiBuZWVkIHRvIGluZGljYXRlIHdoaWNoIGlzIHRvIGJlIHByb2Nlc3NlZDsgZGVmYXVsdHMgdG8gdGhlIGZpcnN0IGVsZW1lbnQgb2YgdGhlIGxpc3QKIyBzbywgaXQgaXMgZWFzaWVyIGlmIHlvdSBqdXN0IGhhdmUgb25lIGVsZW1lbnQgaW4gdGhlIGZvbGRlcgppdGVtX3RvX3Byb2Nlc3MgPC0gaXRlbSA8LSBOQV9pbnRlZ2VyXwppZighcHJvY2Vzc19iYXRjaCkge2l0ZW0gPC0gaWZlbHNlKGlzLm5hKGl0ZW1fdG9fcHJvY2VzcyksIDEsIGl0ZW1fdG9fcHJvY2Vzcyl9CmlmKCFpcy5uYShpdGVtKSl7CiAgcGh5c2VxX2xpc3QgPC0gcGh5c2VxX2xpc3RbaXRlbV0KICB9CgoKIyBzaG91bGQgY2hlY2sgaWYgdGhpcyBpcyB0b28gbGFyZ2UKIyBzaXplX2xpbWl0IApzaXplX2xpbWl0IDwtIDUwMGU2TApwaHlzZXFfc2l6ZSA8LSBvYmpfc2l6ZShwaHlzZXFfbGlzdCkKc2l6ZV93YXJuaW5nIDwtIGlmZWxzZShhcy5udW1lcmljKHBoeXNlcV9zaXplKT5zaXplX2xpbWl0LAogICAgICAgICAgICAgICAgICAgICAgICJXQVJOSU5HOiB0b28gbXVjaCBkYXRhIHRvIHByb2Nlc3MiLAogICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJvYmplY3Qgc2l6ZSIsIGFzLmNoYXJhY3RlcihwaHlzZXFfc2l6ZSksICIgQiIsIHNlcCA9ICIgIikpIAojIGxvYWQgdGhlIHN0dWR5IG1ldGFkYXRhIChpZiBub3QgYXZhaWxhYmxlIG9yIG5vdCBwcm9wZXJseSBmb3JtYXR0ZWQgdGhlIHNjcmlwdCB3aWxsIGNyZWF0ZSBhIHN1aXRhYmxlIGRhdGEgZnJhbWUpCiMgbm8gaW5wdXQgcmVxdWlyZWQsIHNob3VsZCB3b3JrIHdpdGggZGVmYXVsdHMKc3R1ZHlfbWV0YWRhdGEgPC0gbG9hZF9tZXRhZGF0YSgpCiMgcHJpbnQgdGhlIHN0dWR5IG1ldGFkYXRhCnN0dWR5X21ldGFkYXRhICU+JSAKICBkcGx5cjo6c2VsZWN0KGxhYmVsOnN0dWR5SWQsIHNhbXBsZXMsIHNob3J0X2Rlc2NyLCB0eXBlLCByZWZfc2hvcnQpCgpsb2FkaW5nX3JlcG9ydCA8LSB0aWJibGUoIG9iamVjdHNfdG9fcHJvY2VzcyA9IGMobmFtZXNfbGlzdCxzaXplX3dhcm5pbmcpKQppZih2ZXJib3NlX291dHB1dCkgcHJpbnQobG9hZGluZ19yZXBvcnQpCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KQppZihrZWVwX3RpbWUpIHRvYygpCgpgYGAKCiMjIEZpbHRlcmluZy4gIAoKRmlsdGVyaW5nIG9wdGlvbmFsbHkgZmlsdGVycyBzYW1wbGVzIGFuZCB0YXhhIChvbiB0aGUgYmFzaXMgb2YgdGF4b25vbXkgYW5kIG9mIGEgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcikgYW5kIHJldHVybnMgdGhlIGZpbHRlcmVkIGBwaHlsb3NlcWAgb2JqZWN0cyBpbiBhIHNlcGFyYXRlIGxpc3QuIFRheG9ub21pYyBhZ2dsb21lcmF0aW9uIGlzIG9wdGlvbmFsbHkgY2FycmllZCBvdXQuIFRoZSBvcHRpb25zIGZvciBmaWx0ZXJpbmcgYXJlIHRob3NlIGRlZmluZWQgaW4gdGhlIG9wdGlvbnMgc2VjdGlvbi4gIAoKIyMjIEZpbHRlciBzYW1wbGVzLiAgCgpTYW1wbGVzIHdpdGggbGVzcyB0aGFuIGEgZ2l2ZW4gbnVtYmVyIG9mIHNlcXVlbmNlcyBhcmUgZGlzY2FyZGVkIGZpcnN0LiBBcyBhIGNvbnNlcXVlbmNlLCBhIGdpdmVuIGBwaHlsb3NlcWAgb2JqZWN0IG1heSBmYWxsIG91dCBiZWxvdyB0aGUgc2V0IG1pbmltdW0gb2Ygc2FtcGxlcy4gVGhlcmVmb3JlIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyBwZXIgb2JqZWN0IGlzIGNoZWNrZWQgYWdhaW4uCgpgYGB7ciBmaWx0ZXJfYW5kX2dsb21fc2FtcGxlcywgZHBpID0gOTZ9CmlmKGtlZXBfdGltZSkgdGljKCJmaWx0ZXIgYW5kIGdsb20gc2FtcGxlcyIpCiMgZmlyc3QgcHJ1bmUgb2JqZWN0cyB3aXRoIGxlc3MgdGhhbiBtaW5fc2VxcyBzZXF1ZW5jZXMKcGh5c2VxX2xpc3RfMCAgPC0gcGh5c2VxX2xpc3QKIyBjcmVhdGUgZmlyc3Qgc3RlcCBvZiB0aGUgZmlsdGVyaW5nIHJlcG9ydApmaWx0ZXJpbmdfcmVwb3J0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChwaHlzZXFfbGlzdCkpCmZpbHRlcmluZ19yZXBvcnRfMCA8LSBtYXAocGh5c2VxX2xpc3QsIHJlcG9ydF9zdGVwXzApCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdCkpewogIGNhdCgicHJvY2Vzc2luZyAiLCBpLCAiIG9mICIsIGxlbmd0aChwaHlzZXFfbGlzdCksICJcbiIpCiAgIyBzZXQgcGxvdF9lY2RmPUYgZm9yIGZhc3RlciBleGVjdXRpb24KICBwaHlzZXFfbGlzdF8wW1tpXV0gPC0gcHJ1bmVfc2FtcGxlc19ieV9zaXplKHBoeXNlcV9saXN0W1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcyhwaHlzZXFfbGlzdClbaV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF9lY2RmID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5zZXFzID0gbWluX3NlcXMpCn0KY2F0KCIuLi5kb25lIHBydW5pbmcgc2FtcGxlcy4uLlxuIikKIyBub3cgcmVjaGVjayBpZiBhbGwgb2JqZWN0cyBoYXZlIGVub3VnaCBzYW1wbGVzCnBoeXNlcV9saXN0XzEgPC0gcmVtX2xvd19zYW1wbGVfb2JqKHBoeXNlcV9saXN0XzAsIG1zID0gbWluX3NhbXBsZXMpCiMgY3JlYXRlIHNlY29uZCBzdGVwIG9mIHRoZSBmaWx0ZXJpbmcgcmVwb3J0CnN0YWdlX25hbWUgPSAicHJ1bmUgc2FtcGxlcyIKZm9yKGkgaW4gc2VxX2Fsb25nKHBoeXNlcV9saXN0XzApKXsKICBpZihuYW1lcyhwaHlzZXFfbGlzdF8wKVtpXSAlaW4lIG5hbWVzKHBoeXNlcV9saXN0XzEpKXsKICAgIG5hbWUgPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KICAgIHN0YWdlIDwtIHJlcG9ydF9zdGVwX24obXlfcGh5c2VxID0gcGh5c2VxX2xpc3RfMVtbbmFtZV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbXlfcGh5c2VxX28gPSBwaHlzZXFfbGlzdF8wW1tpXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFnZV9uYW1lID0gc3RhZ2VfbmFtZSkgCiAgfSBlbHNlIHsKICAgIHN0YWdlIDwtIGMoc3RhZ2VfbmFtZSwKICAgICAgICAgICAgICBzYW1wbGVzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgc2VxdWVuY2VzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgdGF4YSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2VxID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgcHJvcF90YXhhID0gTkFfcmVhbF8pCiAgfQogIGZpbHRlcmluZ19yZXBvcnRbW2ldXSA8LSByYmluZChzdGFnZV8wID0gYXMuY2hhcmFjdGVyKGZpbHRlcmluZ19yZXBvcnRfMFtbaV1dKSwgc3RhZ2VfMSA9c3RhZ2UpCiAgbmFtZXMoZmlsdGVyaW5nX3JlcG9ydClbaV0gPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KfQoKCmlmKHZlcmJvc2Vfb3V0cHV0KSBwcmludChmaWx0ZXJpbmdfcmVwb3J0KQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCiMjIFRheG9ub21pYyBmaWx0ZXJpbmcuICAKCkEgbnVtYmVyIG9mIHRheG9ub21pYyBmaWx0ZXJpbmcgb3BlcmF0aW9ucyBpcyBwZXJmb3JtZWQgYXQgdGhpcyBzdGFnZSAoZGVwZW5kaW5nIG9uIHRoZSBuYXR1cmUgb2YgdGhlIG9iamVjdCBhbmQgZmlsdGVyaW5nIG9wdGlvbnMsIHNldCBpbiB0aGUgc2V0dGluZ19vcHRpb25zIGNodW5rKS4gUGxvdHMgYXJlIG9wdGlvbmFsbHkgcHJvZHVjZWQgYW5kIHNhdmVkLiAgCgpgYGB7ciB0YXhvbm9taWNfZmlsdGVyaW5nLCBkcGkgPSA5Nn0KaWYoa2VlcF90aW1lKSB0aWMoInRheG9ub21pYyBmaWx0ZXJpbmcsIHN0ZXAgMSIpCgojIGNoZWNrIHRoZSBuYXR1cmUgb2YgdGhlIHRheG9ub21pYyB0YWJsZQojIGluIEZNQk4geW91IGVpdGhlciBoYXZlIEFTViB0YWJsZXMgb3IgdGF4IHRhYmxlcyB3aXRoIGFnZ2xvbWVyYXRpb24gYXQgdGhlIHNwZWNpZXMgbGV2ZWwgb3IgYWJvdmUKIyB3aGljaCBhcmUgdGhlIG5hbWVzIG9mIHRoZSB0YXggbGV2ZWxzPwp0YXhfbGV2ZWxzIDwtIGMoImRvbWFpbiIsInBoeWx1bSIsImNsYXNzIiwib3JkZXIiLCJmYW1pbHkiLCJnZW51cyIsInNwZWNpZXMiKQojIGdldC9zZXQgcmFuayBuYW1lcyAodGhpcyBpcyBuZWNlc3NhcnkgYmVjYXVzZSBwaHlsb3NlcSBvYmplY3RzIGZyb20gRk1CTiBvciBmcm9tIHRoZQojIGJpb2NvbmR1Y3RvciBwaXBlbGluZSB3aXRoIFNJTFZBIGhhdmUgZGlmZmVyZW50IG5hbWVzKQoKcGh5c2VxX2xpc3RfMiA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMSwgZ3NldF9yYW5rX25hbWVzLCB0YXhfbGV2ZWxzID0gdGF4X2xldmVscykKIyByZW1vdmUgdW5jaGFyYWN0ZXJpemVkIHRheGEgaW4gYWxsIHBoeWxvc2VxIG9iamVjdHMgKHVzaW5nIGEgZnVuY3Rpb25hbCkKcGh5c2VxX2xpc3RfMyA8LSBwaHlzZXFfbGlzdF8yCmlmKHJtX3VuY2hhcil7CiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMiwgc3Vic2V0X3RheGEsICFpcy5uYShkb21haW4pICYgIWRvbWFpbiAlaW4lIGMoIiIsICJ1bmNoYXJhY3Rlcml6ZWQiKSkKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCBzdWJzZXRfdGF4YSwgIWlzLm5hKHBoeWx1bSkgJiAhcGh5bHVtICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKQp9CgojIG9wdGlvbmFsbHkgcmVtb3ZlIEV1a2FyeW90ZXMKaWYocm1fZXVrKSB7CiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMywgc3Vic2V0X3RheGEsIGRvbWFpbiAhPSJFdWthcnlvdGEiKQp9CgojIG9wdGlvbmFsbHkgcmVtb3ZlIGNobG9yb3BsYXN0cyBhbmQgbWl0b2Nob25kcmlhIAppZihybV9jaGxtaXQpIHsKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCByZW1vdmVfQ2hsX01pdCkKfQoKIyB1c2UgbG9va3VwIHRhYmxlIHRvIGNoYW5nZSB0YXhvbm9teSBvZiBMYWN0b2JhY2lsbHVzOyBuZWNlc3NhcnkgZm9yIHBoeWxvc2VxIG9iamVjdHMgcHJvZHVjZWQgd2l0aCBTSUxWQQojIGJ1dCBub3QgZm9yIG9iamVjdHMgZXh0cmFjdGVkIGZyb20gRk1CTiAod2hpY2ggYXJlIHRyYW5zZm9ybWVkIGJlZm9yZSBleHRyYWN0aW9uKTsgd2lsbCBhbHNvIGNoYW5nZSB0aGUgCiMgdGF4YSBuYW1lcyBmb3IgQVNWcwojIE1BWSBCRSBTTE9XCmlmKHVzZV9sb29rdXApewogICMgbG9vcCBvdmVyIHRoZSBsaXN0IG9mIHBoeWxvc2VxIG9iamVjdHMKICBmb3IoaSBpbiBzZXFfYWxvbmcocGh5c2VxX2xpc3RfMykpewogICAgIyBjaGVjayB0aGUgbGVuZ3RoIG9mIHRoZSBuYW1lcyBvZiB0aGUgdGF4YTsgaWYgPDEwMCBpdCBpcyBmcm9tIEZNQk4sIGJyZWFrCiAgICBpZihtZWFuKHNhcHBseSh0YXhhX25hbWVzKHBoeXNlcV9saXN0XzNbW2ldXSksIG5jaGFyKSxuYS5ybSA9IFQpPDEwMCl7CiAgICAgIG5leHQKICAgIH0gZWxzZSB7CiAgICAgICMgY2hhbmdlIHRoZSBuYW1lcwogICAgICB0bmFtZXMgPC0gc3RyX2MoIkFTViIsc2VxKDE6bnRheGEocGh5c2VxX2xpc3RfM1tbaV1dKSkpCiAgICAgIHRheGFfbmFtZXMocGh5c2VxX2xpc3RfM1tbaV1dKTwtdG5hbWVzCiAgICAgICMgZ2V0IHNwZWNpZXMgdG8gY2hhbmdlCiAgICAgIHRheGFfdGFibGUgPC0gYXMuZGF0YS5mcmFtZShhcyh0YXhfdGFibGUocGh5c2VxX2xpc3RfM1tbaV1dKSwibWF0cml4IikpCiAgICAgIHRheGFfdGFibGUgPC0gdGF4YV90YWJsZSAlPiUgbXV0YXRlKGlkID0gc3RyX2MoZ2VudXMsIHNwZWNpZXMsIHNlcCA9ICIgIikpCiAgICAgIHRheGFfdGFibGUgPC0gbGVmdF9qb2luKHRheGFfdGFibGUsIGxvb2t1cF90YWJsZSkKICAgICAgbl9jaGFuZ2VzIDwtIHN1bSghaXMubmEodGF4YV90YWJsZSRuZXdfc3BlY2llcykpCiAgICAgIHRheGFfdGFibGUgPC0gdGF4YV90YWJsZSAlPiUgbXV0YXRlKHNwZWNpZXMgPSBpZl9lbHNlKCFpcy5uYShuZXdfc3BlY2llcyksIG5ld19zcGVjaWVzLCBzcGVjaWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VudXMgPSBpZl9lbHNlKCFpcy5uYShuZXdfZ2VudXMpLCBuZXdfZ2VudXMsIGdlbnVzKQogICAgICApCiAgICAgICMgcmVtb3ZlIGNvbHVtbnMKICAgICAgdGF4YV90YWJsZSA8LSBkcGx5cjo6c2VsZWN0KHRheGFfdGFibGUsIGRvbWFpbjpzcGVjaWVzKQogICAgICAjIG5vdyBjaGFuZ2UgZ2VudXMgZm9yIExhY3RvYmFjaWxsdXMgd2l0aCBubyBzcGVjaWVzCiAgICAgIHRvX2NoYW5nZV9sYiA8LSB3aGljaCgodGF4YV90YWJsZSRnZW51cyA9PSAiTGFjdG9iYWNpbGx1cyIpICYgaXMubmEodGF4YV90YWJsZSRzcGVjaWVzKSkKICAgICAgdGF4YV90YWJsZSRnZW51c1t0b19jaGFuZ2VfbGJdPC0iTGFjdG9iYWNpbGx1cyBjb21wbGV4IgogICAgICAjIHJlcGxhY2UgTGV1Y29ub3N0b2NhY2VhZSB3aXRoIExhY3RvYmFjaWxsYWNlYWUKICAgICAgbl9sZXVjIDwtIG5yb3coZHBseXI6OmZpbHRlcih0YXhhX3RhYmxlLCBmYW1pbHkgPT0gIkxldWNvbm9zdG9jYWNlYWUiKSkKICAgICAgdGF4YV90YWJsZSA8LSB0YXhhX3RhYmxlICU+JSBtdXRhdGUoZmFtaWx5ID0gaWZlbHNlKGZhbWlseSA9PSAiTGV1Y29ub3N0b2NhY2VhZSIsICJMYWN0b2JhY2lsbGFjZWFlIiwgZmFtaWx5KSkKICAgICAgdGF4YV90YWJsZSA8LSBhcy5tYXRyaXgodGF4YV90YWJsZSkKICAgICAgcm93bmFtZXModGF4YV90YWJsZSk8LXRuYW1lcwogICAgICB0YXhfdGFibGUocGh5c2VxX2xpc3RfM1tbaV1dKTwtdGF4YV90YWJsZQogICAgICBpZih2ZXJib3NlX291dHB1dCl7CiAgICAgICAgY2F0KG5hbWVzKHBoeXNlcV9saXN0XzMpW2ldLCI6IGNoYW5nZWQgIiwgbl9jaGFuZ2VzK25fbGV1YywgIiB0YXhhXG4iLCBzZXAgPSIiKQogICAgICB9CiAgICB9CiAgfQp9CgojIHJlbW92ZSBmdXJ0aGVyIHRheGEgd2hpY2ggYXJlIHVuY2hhcmFjdGVyaXplZCBhdCB0aGUgZmFtaWx5IHRvIGNsYXNzIGxldmVsCgojIG5vdGUgZm9yIHNlbGY6IG1pZ2h0IGltcHJvdmUgaXQgYnkgc2V0dGluZyB0aGUgbGV2ZWwgYXQgb3IgYWJvdmUgd2hpY2ggdW5jaGFyYWN0ZXJpemVkIHRheGEgY2FuIGJlIHJlbW92ZWQKIyByYXRoZXIgdGhhbiB1c2luZyBhIFQvRiBmbGFnCmlmKGFib3ZlX2dlbnVzX2ZsYWcpewogIHBoeXNlcV9saXN0XzMgPC0gbGFwcGx5KHBoeXNlcV9saXN0XzMsIHN1YnNldF90YXhhLCAhaXMubmEoY2xhc3MpICYgIWNsYXNzICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKSAjIENsYXNzCiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMywgc3Vic2V0X3RheGEsICFpcy5uYShvcmRlcikgJiAhb3JkZXIgJWluJSBjKCIiLCAidW5jaGFyYWN0ZXJpemVkIikpICMgT3JkZXIKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCBzdWJzZXRfdGF4YSwgIWlzLm5hKGZhbWlseSkgJiAhZmFtaWx5ICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKSAjIEZhbWlseQp9CgojIG9wdGlvbmFsbHkgcGVyZm9ybSB0YXhvbm9taWMgYWdnbG9tZXJhdGlvbiwgCgojIG1heSB0YWtlIHNvbWUgdGltZTsgY2FuIGJlIG1hZGUgZmFzdGVyIHdpdGggcGx5ciBmdW5jdGlvbnMgdXNpbmcgcGFyYWxsZWxpemF0aW9uIG9yIHdpdGggZnVycnIKcGh5c2VxX2xpc3RfNCA8LSBwaHlzZXFfbGlzdF8zCmlmKHRheGdsb20gIT0gIm5vbmUiKXsKICBwaHlzZXFfbGlzdF80IDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCB0YXhfZ2xvbV9uYW1lX2NoYW5nZSwgdGF4YV9nbG9tID0gdGF4Z2xvbSkKfQoKIyBjcmVhdGUgdGhpcmQgc3RlcCBvZiB0aGUgZmlsdGVyaW5nIHJlcG9ydApzdGFnZV9uYW1lID0gInRheG9ub21pYyBmaWx0ZXIrZ2xvbSIKZm9yKGkgaW4gc2VxX2Fsb25nKHBoeXNlcV9saXN0XzApKXsKICBpZihuYW1lcyhwaHlzZXFfbGlzdF8wKVtpXSAlaW4lIG5hbWVzKHBoeXNlcV9saXN0XzEpKXsKICAgIG5hbWUgPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KICAgIHN0YWdlIDwtIHJlcG9ydF9zdGVwX24obXlfcGh5c2VxID0gcGh5c2VxX2xpc3RfNFtbbmFtZV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbXlfcGh5c2VxX28gPSBwaHlzZXFfbGlzdF8wW1tpXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFnZV9uYW1lID0gc3RhZ2VfbmFtZSkgCiAgfSBlbHNlIHsKICAgIHN0YWdlIDwtIGMoc3RhZ2VfbmFtZSwKICAgICAgICAgICAgICBzYW1wbGVzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgc2VxdWVuY2VzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgdGF4YSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2VxID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgcHJvcF90YXhhID0gTkFfcmVhbF8pCiAgfQogIGZpbHRlcmluZ19yZXBvcnRbW2ldXSA8LSByYmluZChmaWx0ZXJpbmdfcmVwb3J0W1tpXV0sIHN0YWdlXzMgPXN0YWdlKQp9CgppZihrZWVwX3RpbWUpIHRvYygpCgppZihrZWVwX3RpbWUpIHRpYygiY2FsY3VsYXRlIGRpdmVyc2l0eSBwcmUtZmlsdGVyIikKCiMgQ2FsY3VsYXRlIGRpdmVyc2l0eSBwcmlvciB0byBmaWx0ZXJpbmcgZm9yIHByZXZhbGVuY2UgYW5kIGFidW5kYW5jZQoKZGl2X2VzdF9wcmVmaWx0ZXIgPC0gbWFwX2RmcihwaHlzZXFfbGlzdF80LCBwaHlsb3NlcTo6ZXN0aW1hdGVfcmljaG5lc3MsIHNwbGl0ID0gRiwgbWVhc3VyZT1jKCJPYnNlcnZlZCIsIkNoYW8xIiwiU2hhbm5vbiIpKQpkaXZfZXN0X3ByZWZpbHRlciA8LSBtdXRhdGUoZGl2X2VzdF9wcmVmaWx0ZXIsIFBpZWxvdV9KID0gU2hhbm5vbi9sb2coT2JzZXJ2ZWQpKQojIGNhbGN1bGF0ZSBhbmQgYWRkIGF2ZSBCcmF5LUN1cnRpcyBkaXNzaW1pbGFyaXR5Cm1lYW5iY2Rpc3QgPC0gbWFwKHBoeXNlcV9saXN0XzQsIHBoeWxvc2VxOjpkaXN0YW5jZSwgbWV0aG9kPSJicmF5IikKZGl2X2VzdF9wcmVmaWx0ZXIkYXZlX0JDIDwtIHVubGlzdChtYXAobWVhbmJjZGlzdCwgbWVhbikpCgpyb3cubmFtZXMoZGl2X2VzdF9wcmVmaWx0ZXIpIDwtIG5hbWVzKHBoeXNlcV9saXN0XzQpCiMgc2F2ZSBmb3IgZnVydGhlciB1c2UKc2F2ZShkaXZfZXN0X3ByZWZpbHRlciwgCiAgICAgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9kaXZwcmVmaWx0ZXIuUmRhdGEiLHNlcD0iIikpCgppZihrZWVwX3RpbWUpIHRvYygpCgojIFByZXZhbGVuY2UgYW5kIGFidW5kYW5jZSBmaWx0ZXIKaWYoa2VlcF90aW1lKSB0aWMoInRheG9ub21pYyBmaWx0ZXJpbmcsIHN0ZXAgMiIpCgojIE5PVEUgdXNpbmcgYSBwcmV2YWxlbmNlIGZpbHRlciBiYXNlZCBvbiBmcmFjdGlvbiBtYXkgYmUgd3JvbmcgZm9yIHN0dWRpZXMgd2l0aCBsYXJnZSAKIyBudW1iZXIgb2Ygc2FtcGxlcywgaW4gd2hpY2ggb25lIG1pZ2h0IHdhbnQgdG8gcmV0YWluIHRheGEgd2hpY2ggYXBwZWFyIGluID4xMCBzYW1wbGVzIAoKcGh5c2VxX2xpc3RfNSA8LSBwaHlzZXFfbGlzdF80CiMgd2lsbCBiZSBza2lwcGVkIGlmIGZpbHRlck9UVXMgPT0gRgpub2RlX3N0YXRfbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgocGh5c2VxX2xpc3RfNSkpCiMgYSBsaXN0IHdoaWNoIHdpbGwgaG9zdCBub2RlIHN0YXRzLCBsaWtlIHRoZSBwcmV2YWIgZGF0YSwgaW5pdGlhbGx5IGVtcHR5LAojIGlmIG5vdGhpbmcgaXMgYWRkZWQgYXQgdGhpcyBzdGFnZSwgbm9kZSBzdGF0cyB3aWxsIGJlIGFkZGVkIGF0IGEgbGF0ZXIgc3RhZ2UKaWYoZmlsdGVyT1RVcyl7CiAgcHJldl9hYl9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChwaHlzZXFfbGlzdF80KSkKICAjIHdpbGwgaG9zdCB0aGUgbGlzdHMgd2l0aCB0aGUgcmVzdWx0cwogIGZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF80KSl7CiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UgZmlsdGVyLCBwaHlzZXEgIixpLCIgb2YgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKHBoeXNlcV9saXN0XzQpLCJcbiIpCiAgICBwcmV2X2FiX2xpc3RbW2ldXSA8LSBmaWx0ZXJfYnlfcHJldl9hYigKICAgICAgbXlwaHlzZXEgPSBwaHlzZXFfbGlzdF80W1tpXV0sCiAgICAgIG5hbWUgPSBuYW1lcyhwaHlzZXFfbGlzdF80KVtpXQogICAgKQogICAgbmFtZXMocHJldl9hYl9saXN0KVtpXTwtbmFtZXMocGh5c2VxX2xpc3RfNClbaV0KICAgICMgc2F2ZSB0aGUgcHJvY2Vzc2VkIHBoeWxvc2VxCiAgICBwaHlzZXFfbGlzdF81W1tpXV0gPC0gcHJldl9hYl9saXN0W1tpXV1bWzFdXQogICAgIyBzYXZlIHByZXYgYWIgdGFibGUKICAgIG5vZGVfc3RhdF9saXN0W1tpXV0gPC0gcHJldl9hYl9saXN0W1tpXV1bWzRdXQogICAgbmFtZXMobm9kZV9zdGF0X2xpc3QpW2ldIDwtIG5hbWVzKHBoeXNlcV9saXN0XzQpW2ldCiAgfQp9CiMgb3B0aW9uYWxseSBzYXZlIHRoZSBwcmV2X2FiX2xpc3QKaWYoc2F2ZV9wcmV2X2FiX2xpc3QpIHNhdmUocHJldl9hYl9saXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgbmFtZSwgIl9wcmV2YWJsLlJkYXRhIixzZXA9IiIpKQoKIyBjcmVhdGUgZm91cnRoIHN0ZXAgb2YgdGhlIGZpbHRlcmluZyByZXBvcnQKc3RhZ2VfbmFtZSA9ICJwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UiCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF8wKSl7CiAgaWYobmFtZXMocGh5c2VxX2xpc3RfMClbaV0gJWluJSBuYW1lcyhwaHlzZXFfbGlzdF8xKSl7CiAgICBuYW1lIDwtIG5hbWVzKHBoeXNlcV9saXN0XzApW2ldCiAgICBzdGFnZSA8LSByZXBvcnRfc3RlcF9uKG15X3BoeXNlcSA9IHBoeXNlcV9saXN0XzVbW25hbWVdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG15X3BoeXNlcV9vID0gcGh5c2VxX2xpc3RfMFtbaV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhZ2VfbmFtZSA9IHN0YWdlX25hbWUpIAogIH0gZWxzZSB7CiAgICBzdGFnZSA8LSBjKHN0YWdlX25hbWUsCiAgICAgICAgICAgICAgc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHNlcXVlbmNlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHRheGEgPSBOQV9yZWFsXywKICAgICAgICAgICAgICBwcm9wX3NhbXBsZXMgPSBOQV9yZWFsXywKICAgICAgICAgICAgICBwcm9wX3NlcSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3BfdGF4YSA9IE5BX3JlYWxfKQogIH0KICBmaWx0ZXJpbmdfcmVwb3J0W1tpXV0gPC0gcmJpbmQoZmlsdGVyaW5nX3JlcG9ydFtbaV1dLCBzdGFnZV80ID1zdGFnZSkKfQoKIyByZW1vdmUgdW5uZWVkZWQgb2JqZWN0cwpybShwaHlzZXFfbGlzdF8xLCBwaHlzZXFfbGlzdF8zLCBwaHlzZXFfbGlzdF80LCBwcmV2X2FiX2xpc3QsIHRheGFfdGFibGUpCiMgY3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSByZXBvcnQKZmlsdGVyaW5nX3JlcG9ydF9kZiA8LSBtYXBfZGZyKGZpbHRlcmluZ19yZXBvcnQsIGFzLmRhdGEuZnJhbWUsIC5pZCA9ICJkYXRhc2V0IikKZmlsdGVyaW5nX3JlcG9ydF9kZiA8LSBmaWx0ZXJpbmdfcmVwb3J0X2RmICU+JSAKICBtdXRhdGUoZGF0YV90eXBlID0gaWZfZWxzZShzdHJfZGV0ZWN0KGRhdGFzZXQsICJGTUJOIiksICJGTUJOIiwgIkFTViIpKSAlPiUKICBtdXRhdGUoZHBseXI6OmFjcm9zcyguY29scyA9IHNhbXBsZXM6cHJvcF90YXhhLCBhcy5udW1lcmljKSkgJT4lCiAgbXV0YXRlKHN0YWdlID0gYXNfZmFjdG9yKHN0YWdlKSkKaWYodmVyYm9zZV9vdXRwdXQpIHsKICBwcmludChmaWx0ZXJpbmdfcmVwb3J0X2RmKQogIHByaW50KGZpbHRlcmluZ19yZXBvcnRfZGYgJT4lCiAgICAgICAgICBkcGx5cjo6ZmlsdGVyKHN0YWdlICE9ICJvcmlnaW5hbCIgJiBzdGFnZSAhPSAicHJ1bmUgc2FtcGxlcyIpICU+JQogICAgICAgICAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRhdGFfdHlwZSwgeSA9IHByb3BfdGF4YSkpICsKICAgICAgICAgIGZhY2V0X3dyYXAofnN0YWdlKSArCiAgICAgICAgICBnZW9tX2JveHBsb3QoKSArCiAgICAgICAgICBsYWJzKHggPSAiZGF0YSB0eXBlIiwgeSA9ICJwcm9wLiB0YXhhIGxlZnQiKSkKICBzdW1tX2ZpbHRlcmluZyA8LSBmaWx0ZXJpbmdfcmVwb3J0X2RmICU+JQogICAgZ3JvdXBfYnkoZGF0YV90eXBlLCBzdGFnZSkgJT4lCiAgICBzdW1tYXJpemUobWluX3NlcSA9IG1pbihwcm9wX3NlcSksIAogICAgICAgICAgICAgIG1heF9zZXEgPSBtYXgocHJvcF9zZXEpLAogICAgICAgICAgICAgIG1pbl90YXhhID0gbWluKHByb3BfdGF4YSksCiAgICAgICAgICAgICAgbWF4X3RheGEgPSBtYXgocHJvcF90YXhhKSkKICBwcmludChzdW1tX2ZpbHRlcmluZykKfQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKClRoZSByZWR1Y3Rpb24gaW4gbnVtYmVyIG9mICJ0YXhhIiBpcyBkcmFtYXRpYyBpbiBtb3N0IGNhc2VzIChhIHByb3BvcnRpb24gZnJvbSAwLjAxMCB0byAwLjIxIGxlZnQpLCB3aGlsZSB0aGUgcHJvcG9ydGlvbiBvZiBzZXF1ZW5jZXMgbGVmdCBpcyBiZXR3ZWVuIDAuOTg0IGFuZCAwLjk5OS4KCiMgSW5mZXJyaW5nIHRoZSBuZXR3b3Jrcy4KCkkgd2lsbCBub3cgdHJ5IE1pY3JvYmlhbCBBc3NvY2lhdGlvbiBOZXR3b3JrIGluZmVyZW5jZSwgd2l0aCB0aGUgbWV0aG9kcyBhbmQgcGFyYW1ldGVycyBzcGVjaWZpZWQgaW4gdGhlIG9wdGlvbnMgY2h1bmsuIFNlcGFyYXRlIGxpc3RzIHdpbGwgYmUgZ2VuZXJhdGVkIGZvciBlYWNoIG1ldGhvZC4gIApBbiBlcnJvciB0cmFwcGluZyByb3V0aW5lIGhhcyBiZWVuIGltcGxlbWVudGVkOiBpZiBNQU4gZXN0aW1hdGlvbiBmYWlscyBhIHRyeS1lcnJvciBvYmplY3QgcmF0aGVyIHRoYW4gYW4gb2JqZWN0IG9mIGNsYXNzIG1pY3JvTmV0IHdpbGwgYmUgcmV0dXJuZWQuICAKQWxsIHRoZSByZXR1cm5lZCBvYmplY3RzIHdpbGwgYmUgc2F2ZWQgaW4gYSBsaXN0ICgxIHNsb3QgZm9yIGVhY2ggcGh5bG9zZXEgb2JqZWN0LCBlYWNoIHdpdGggMSBzbG90IGZvciBlYWNoIGluZmVyZW5jZSBtZXRob2QpLiAgCgpgYGB7ciBNQU5faW5mZXJlbmNlXzEsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiTWljcm9iaWFsIGFzc29jaWF0aW9uIG5ldHdvcmsgaW5mZXJlbmNlIikKaWYodmVyYm9zZV9vdXRwdXQpIGNhdCgiUGxlYXNlIGJlIHBhdGllbnQsIHRoaXMgd2lsbCB0YWtlIGEgd2hpbGUuLi5cbiIpCiMgbGlzdCBmb3IgcmVzdWx0cywgMSBzbG90IGZvciBlYWNoIG9iamVjdApNQU5faW5mX3Jlc3VsdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHBoeXNlcV9saXN0XzUpKQojIGNyZWF0ZSBsaXN0IGZvciBtZXRob2RzLCAxIHNsb3QgZm9yIGVhY2ggbWV0aG9kCmluZl9tZXRoX2xpc3QgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGluZl9tZXRob2RzKSkKCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF81KSl7CiAgbmFtZSA8LSBuYW1lcyhwaHlzZXFfbGlzdF81KVtpXQogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoIlxuIiwgIkluZmVycmluZyBuZXR3b3JrKHMpIGZvciAiLCBuYW1lLCAiLCAiLCBpLCAib2YgIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgocGh5c2VxX2xpc3RfNSksICJcbiIsIHNlcD0iICIpCiAgaWYoa2VlcF90aW1lKSB7CiAgICB0aWNtZXNzYWdlX29iamVjdCA8LSBwYXN0ZSgibWljcm9iaWFsIGFzc29jaWF0aW9uIG5ldHdvcmsgaW5mZXJlbmNlLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSwgIiwgIiwgaSwgIiBvZiAiLCBsZW5ndGgocGh5c2VxX2xpc3RfNSksIHNlcCA9ICIiKQogICAgdGljKHRpY21lc3NhZ2Vfb2JqZWN0KQogIH0KICBmb3IoaiBpbiBzZXFfYWxvbmcoaW5mX21ldGhvZHMpKXsKICAgIGlmKGtlZXBfdGltZSl7CiAgICAgIHRpY21lc3NhZ2VfbWV0aG9kIDwtIHBhc3RlKCJcbiIsICJpbmZlcmVuY2Ugd2l0aCBtZXRob2QgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5mX21ldGhvZHNbal0sICIsICIsIGosICIgb2YgIiwgbGVuZ3RoKGluZl9tZXRob2RzKSwgc2VwID0gIiIpCiAgICAgIHRpYyh0aWNtZXNzYWdlX21ldGhvZCkKICAgIH0KICAgIGluZm1ldGhvZCA8LSBpbmZfbWV0aG9kc1tbal1dCiAgICBpbmZwYXJhbSA8LSBpbmZfbWV0aG9kc19wYXJhbVtbal1dCiAgIAogICAgIyBpbmZyZW5jZSBpcyBjYXJyaWVkIG91dCBoZXJlIHVzaW5nIGEgdXNlciBkZWZpbmVkIGZ1bmN0aW9uIGxvYWRlZCB3aXRoIHRoZSBgc291cmNlKClgIGNvbW1hbmQgCiAgICAjIHlvdSBkbyBub3QgbmVlZCB0byBwcm92aWRlIG11Y2ggZGV0YWlsLCBldmVyeXRoaW5nIGlzIHRha2VuIGNhcmUgb2YgaW4gdGhlIG9wdGlvbnMKICAgICMgb2YgY291cnNlIGNvdWxkIGJlIGN1c3RvbWl6ZWQgaW4gdGhlIGZ1bmN0aW9uIGNhbGwKICAgIGluZl9tZXRoX2xpc3RbW2pdXSA8LSBpbmZlcl9NQU4obXlwaHlzZXEgPSBwaHlzZXFfbGlzdF81W1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZl9tZXRob2QgPSBpbmZtZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZF9wYXJhbWV0ZXJzID0gaW5mcGFyYW0pCiAgICBuYW1lcyhpbmZfbWV0aF9saXN0KVtqXSA8LSBpbmZtZXRob2QKICAgIGlmKGtlZXBfdGltZSkgdG9jKCkKICB9CiAgTUFOX2luZl9yZXN1bHRzW1tpXV0gPC0gaW5mX21ldGhfbGlzdAogIG5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0gPC0gbmFtZQogIGlmKGtlZXBfdGltZSkgdG9jKCkKfQojIGNyZWF0ZSBhIHJlcG9ydAppbmZlcmVuY2VfcmVwb3J0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aChNQU5faW5mX3Jlc3VsdHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcoTUFOX2luZl9yZXN1bHRzKSl7CiAgaW5mZXJlbmNlX3JlcG9ydFtbaV1dPC1tYXBfZGZyKE1BTl9pbmZfcmVzdWx0c1tbaV1dLCBjbGFzcywgLmlkID0gIm1ldGhvZCIpCiAgbmFtZXMoaW5mZXJlbmNlX3JlcG9ydClbaV08LW5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KfQppbmZlcmVuY2VfcmVwb3J0X2RmIDwtIGJpbmRfcm93cyhpbmZlcmVuY2VfcmVwb3J0LCAuaWQgPSAiZGF0YXNldCIpCgojIHNhdmUgdGhlIGxpc3QgYW5kIGRvIHNvbWUgY2xlYW4tdXAKc2F2ZShNQU5faW5mX3Jlc3VsdHMsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0X2ZvbGRlciwgcGFzdGUob3V0X2ZpbGVuYW1lX3ByZWYsIl9NQU5saXN0LlJkYXRhIikpKQpybShpbmZfbWV0aF9saXN0LCBpbmZlcmVuY2VfcmVwb3J0KQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCiMgQW5hbHl6ZSB0aGUgbmV0d29ya3MuICAKClRoZSBpbmZlcnJlZCBuZXR3b3JrcyB3aWxsIGJlIGFuYWx5emVkIHVzaW5nIE5ldENvTWk6Om5ldEFuYWx5emUoKSBhbmQgdGhlIHJlc3VsdGluZyBvYmplY3RzIHdpbGwgYmUgcHJvY2Vzc2VkIGZ1cnRoZXIuICAKTmV0d29yaywgbm9kZSBhbmQgZWRnZSBzdGF0cyB3aWxsIGJlIGV4dHJhY3RlZCB0byBkYXRhIGZyYW1lcy90aWJibGVzIGZvciBmdXJ0aGVyIHByb2Nlc3NpbmcuICAKQSBmZXcgZXh0cmEgYW5hbHlzZXMgd2lsbCBiZSBjYXJyaWVkIG91dCB1c2luZyBwYWNrYWdlIHBoeWxvc2VxIHRvIGFkZCBkaXZlcnNpdHkgYW5kIGV2ZW5uZXNzIGluZGljZXMgdG8gdGhlIG1ldGFkYXRhLiAgCgpgYGB7ciBhbmFseXplX25ldHdvcmtzLCBkcGkgPSA5Nn0KCmlmKGtlZXBfdGltZSkgdGljKCJjYWxjdWxhdGUgZGl2ZXJzaXR5IHBvc3QtZmlsdGVyIikKCiMgZXN0aW1hdGUgZGl2ZXJzaXR5IGZvciBlYWNoIG9iamVjdCBvZiB0aGUgcGh5c2VxX2xpc3RfNSwgcmV0dXJucyBhIGxpc3Qgd2l0aCB0aGUgcmVzdWx0cwojIGV4dHJhY3QgYW5kIHB1dCB0b2dldGhlciB3aXRoIG1ldGFkYXRhCiMgd2lsbCBnZW5lcmF0ZSB3YXJuaW5ncwoKZGl2X2VzdF9wb3N0ZmlsdGVyIDwtIG1hcF9kZnIocGh5c2VxX2xpc3RfNSwgZXN0aW1hdGVfcmljaG5lc3MsIHNwbGl0ID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYXN1cmVzID0gYygiT2JzZXJ2ZWQiLCJDaGFvMSIsIlNoYW5ub24iKSwgLmlkID0gImxhYmVsIikKZGl2X2VzdF9wb3N0ZmlsdGVyIDwtIG11dGF0ZShkaXZfZXN0X3Bvc3RmaWx0ZXIsIFBpZWxvdV9KID0gU2hhbm5vbi9sb2coT2JzZXJ2ZWQpKQoKIyBjYWxjdWxhdGUgYW5kIGFkZCBhdmVyYWdlIEJyYXktQ3VydGlzIGRpc3NpbWlsYXJpdHkKbWVhbmJjZGlzdCA8LSBtYXAocGh5c2VxX2xpc3RfNSwgcGh5bG9zZXE6OmRpc3RhbmNlLCBtZXRob2Q9ImJyYXkiKQpkaXZfZXN0X3Bvc3RmaWx0ZXIkYXZlX0JDIDwtIHVubGlzdChtYXAobWVhbmJjZGlzdCwgbWVhbikpCgpyb3cubmFtZXMoZGl2X2VzdF9wb3N0ZmlsdGVyKSA8LSBuYW1lcyhwaHlzZXFfbGlzdF81KQojIHNhdmUgZm9yIGZ1cnRoZXIgdXNlCnNhdmUoZGl2X2VzdF9wb3N0ZmlsdGVyLCAKICAgICBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX2RpdnBvc3RmaWx0ZXIuUmRhdGEiLHNlcD0iIikpCgojIGFuIGFsdGVybmF0aXZlIGNvdWxkIGJlIHRvIHVzZSBpdCBvbiBkaXZfZXN0X3ByZWZpbHRlcgpzdHVkeV9tZXRhZGF0YSA8LSBsZWZ0X2pvaW4oc3R1ZHlfbWV0YWRhdGEsIGRpdl9lc3RfcG9zdGZpbHRlcikKCmlmKGtlZXBfdGltZSkgdG9jKCkKCiMgY2FsY3VsYXRlIG5ldHdvcmsgc3RhdGlzdGljcyB3aXRoIG5ldEFuYWx5emUKaWYoa2VlcF90aW1lKSB0aWMoIkNhbGN1bGF0ZSBuZXR3b3JrIHN0YXRpc3RpY3MiKQoKIyB0aGUgbGlzdCBmb3IgdGhlIG1ldGhvZHMgd2l0aGluIHRoZSBkYXRhc2V0Cm5ldF9zdGF0cyA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgoTUFOX2luZl9yZXN1bHRzKSkKIyBuZXRfc3RhdF9yZXN1bHRzIGlzIGEgbGlzdCwgZG8gdGhlIGNhbGN1bGF0aW9uIGZvciBlYWNoIG9mIHRoZSBkYXRhc2V0cywgYWxsIGluZmVyZW5jZSBtZXRob2RzCmZvcihpIGluIHNlcV9hbG9uZyhNQU5faW5mX3Jlc3VsdHMpKXsKICBuYW1lIDwtIG5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJDYWxjdWxhdGluZyBuZXR3b3JrIHN0YXRzIGZvciIsbmFtZSwiXG4iKQogIG5ldF9zdGF0X3Jlc3VsdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKE1BTl9pbmZfcmVzdWx0c1tbaV1dKSkKICBmb3IoaiBpbiBzZXFfYWxvbmcoTUFOX2luZl9yZXN1bHRzW1tpXV0pKXsKICAgIG10aGQgPC0gbmFtZXMoTUFOX2luZl9yZXN1bHRzW1tpXV0pW2pdCiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJtZXRob2QiLG10aGQsIlxuIikKICAgICMgY29uc2lkZXIgcmVkdWNpbmcgYXJndW1lbnQgaHViUXVhbnQgdG8gMC43NS0wLjkwIGRlZmF1bHQgaXMgMC45NSksCiAgICBuZXRfc3RhdF9yZXN1bHRzW1tqXV0gPC0gY2FsY3VsYXRlX25ldF9zdGF0cyhNQU5faW5mX3Jlc3VsdHNbW2ldXVtbal1dKQogIH0KICBuYW1lcyhuZXRfc3RhdF9yZXN1bHRzKTwtbmFtZXMoTUFOX2luZl9yZXN1bHRzW1tpXV0pCiAgbmV0X3N0YXRzW1tpXV0gPC0gbmV0X3N0YXRfcmVzdWx0cwogIG5hbWVzKG5ldF9zdGF0cylbaV08LW5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KfQpybShuZXRfc3RhdF9yZXN1bHRzLCBtZWFuYmNkaXN0LCBtdGhkKQppZihrZWVwX3RpbWUpIHRvYygpCgojIGV4dHJhY3RpbmcgZ2xvYmFsIG5ldHdvcmsgcHJvcGVydGllcwoKaWYoa2VlcF90aW1lKSB0aWMoIkV4dHJhY3Rpb24gZ2xvYmFsIG5ldHdvcmsgcHJvcGVydGllcyIpCiMgZXh0cmFjdGluZyB0aGUgZ2xvYmFsIG5ldHdvcmsgc3RhdHMgCmdsb2JhbF9wcm9wc19saXN0X2EgPC12ZWN0b3IoImxpc3QiLGxlbmd0aChuZXRfc3RhdHMpKQpnbG9iYWxfcHJvcHNfbGlzdF9sIDwtdmVjdG9yKCJsaXN0IixsZW5ndGgobmV0X3N0YXRzKSkgCmdsb2JhbF9wcm9wc19saXN0X2FsbCA8LXZlY3RvcigibGlzdCIsbGVuZ3RoKGluZl9tZXRob2RzKSkKZ2xvYmFsX3Byb3BzX2xpc3RfbGNjIDwtdmVjdG9yKCJsaXN0IixsZW5ndGgoaW5mX21ldGhvZHMpKQpmb3IgKGkgaW4gc2VxX2Fsb25nKG5ldF9zdGF0cykpewogIGRhdGFzZXQgPC0gbmFtZXMobmV0X3N0YXRzKVtpXQogIGZvciAoaiBpbiBzZXFfYWxvbmcobmV0X3N0YXRzW1tpXV0pKXsKICAgIGlmKGNsYXNzKG5ldF9zdGF0c1tbaV1dW1tqXV0pIT0gIm1pY3JvTmV0UHJvcHMiKXsKICAgICAgbmV4dAogICAgfSBlbHNlewogICAgICBubm9kZXMgPC0gc3VtKG5ldF9zdGF0c1tbaV1dW1tqXV0kY2VudHJhbGl0aWVzJGRlZ3JlZTE+MCkKICAgICAgbnRheGEgPC0gbnJvdyhuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxKQogICAgICBucG9zZWRnZSA8LSBzdW0obmV0X3N0YXRzW1tpXV1bW2pdXSRpbnB1dCRhc3NvTWF0MVtsb3dlci50cmkobmV0X3N0YXRzW1tpXV1bW2pdXSRpbnB1dCRhc3NvTWF0MSldPjApCiAgICAgIG5uZWdlZGdlIDwtIHN1bShuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxW2xvd2VyLnRyaShuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxKV08MCkKICAgICAgZXh0cmFfcHJvcF92ZWN0b3IgPC0gYyhubm9kZXMsIG50YXhhLCBucG9zZWRnZSwgbm5lZ2VkZ2UpCiAgICAgIG5hbWVzKGV4dHJhX3Byb3BfdmVjdG9yKSA8LSBjKCJubm9kZXMiLCAibnRheGEiLCAibnBvc2VkZ2UiLCAibm5lZ2VkZ2UiKQogICAgICBnbG9iYWxfcHJvcHNfbGlzdF9hbGxbW2pdXSA8LSBjKHVubGlzdChuZXRfc3RhdHNbW2ldXVtbal1dJGdsb2JhbFByb3BzKSxleHRyYV9wcm9wX3ZlY3RvcikKICAgICAgZ2xvYmFsX3Byb3BzX2xpc3RfbGNjW1tqXV0gPC0gYyh1bmxpc3QobmV0X3N0YXRzW1tpXV1bW2pdXSRnbG9iYWxQcm9wc0xDQyksZXh0cmFfcHJvcF92ZWN0b3IpCiAgICAgIG5hbWVzKGdsb2JhbF9wcm9wc19saXN0X2FsbClbal0gPC0gbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfbGNjKVtqXTwtIG5hbWVzKG5ldF9zdGF0c1tbaV1dW2pdKQogICAgfQogICAgIyBjcmVhdGUgZGF0YSBmcmFtZSB3aXRoIHJlc3VsdHMKICAgIGFsbF9kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfYWxsLCAuaWQgPSAibWV0aG9kIikKICAgIGxjY19kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfbGNjLCAuaWQgPSAibWV0aG9kIikgCiAgfQogIGdsb2JhbF9wcm9wc19saXN0X2FbW2ldXSA8LSBhbGxfZGYKICBnbG9iYWxfcHJvcHNfbGlzdF9sW1tpXV0gPC0gbGNjX2RmCiAgbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfYSlbaV0gPC0gbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfbClbaV0gPC0gbmFtZXMobmV0X3N0YXRzKVtpXQp9CiMgbm90ZSB0aGF0IHN0cl9zdWIgb25seSB3b3JrcyBpZiB5b3UgaGF2ZSA8PTkgaW5mZXJlbmNlIG1ldGhvZHMgaW4gaW5mX21ldGhvZHMKZ2xvYmFsX2FsbF9kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfYSwgLmlkID0gImRhdGFzZXQiKSAKZ2xvYmFsX2xjY19kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfbCwgLmlkID0gImRhdGFzZXQiKSAKCgpnbG9iYWxfYWxsX2RmIDwtIGxlZnRfam9pbihnbG9iYWxfYWxsX2RmLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHN0dWR5X21ldGFkYXRhLGxhYmVsLCBvYmpfdHlwZSwgdGFyZ2V0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbiwgcGxhdGZvcm0sIHNhbXBsZXMsIHR5cGUsIE9ic2VydmVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENoYW8xLCBTaGFubm9uLCBQaWVsb3VfSiwgYXZlX0JDKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiZGF0YXNldCIgPSAibGFiZWwiKSkgJT4lCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfm5hX2lmKC4sSW5mKSkpCmdsb2JhbF9sY2NfZGYgPC0gbGVmdF9qb2luKGdsb2JhbF9sY2NfZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3Qoc3R1ZHlfbWV0YWRhdGEsbGFiZWwsIG9ial90eXBlLCB0YXJnZXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uLCBwbGF0Zm9ybSwgc2FtcGxlcywgdHlwZSwgT2JzZXJ2ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2hhbzEsIFNoYW5ub24sIFBpZWxvdV9KLCBhdmVfQkMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJkYXRhc2V0IiA9ICJsYWJlbCIpKSAlPiUKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+bmFfaWYoLixJbmYpKSkKCiMgYm90aCBtaWdodCBjb250YWluIEluZiB3aGljaCBhcmUgcmVwbGFjZWQgYnkgTkEgKHVzaW5nIGRwbHlyOjpuYV9pZigpKSB0byBiZSBoYW5kbGVkCiMgY29ycmVjdGx5IGlmIGRvaW5nIFBDQSBieSBwYWlyd2lzZSBkZWxldGlvbi4KIyB0aGUgcHJvYmxlbSBvbmx5IG9jY3VycyBpbiBhdlBhdGgxIGFuZCBjbHVzdENvZWYxLCBidXQgSSBhbSBoYW5kbGluZyBpdCB3aXRoIGEgc2NvcGVkIG11dGF0ZQojIGEgYmV0dGVyIHNvbHV0aW9uIG1pZ2h0IGJlIHRoZSB1c2Ugb2YgaGFibGFyOjpyYXRpb25hbGl6ZSgpCgoKIyBzYXZlIHRoZSBkYXRhIGZyYW1lcyBmb3IgZnVydGhlciB1c2UKd3JpdGVfdHN2KGdsb2JhbF9hbGxfZGYsIGZpbGUgPSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfbmV0cHJvcGFsbC50eHQiLHNlcD0iIikpCndyaXRlX3RzdihnbG9iYWxfbGNjX2RmLCBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX25ldHByb3BsY2MudHh0IixzZXA9IiIpKQoKIyBwcmludCBhIHN1bW1hcnkgdGFibGUKZ2xvYmFsX2FsbF9kZgoKcm0oZ2xvYmFsX3Byb3BzX2xpc3RfYSwgZ2xvYmFsX3Byb3BzX2xpc3RfbCwgZ2xvYmFsX3Byb3BzX2xpc3RfYWxsLCAKICAgZ2xvYmFsX3Byb3BzX2xpc3RfbGNjLCBhbGxfZGYsIGxjY19kZiwgbm5vZGVzLCBudGF4YSwgbnBvc2VkZ2UsIG5uZWdlZGdlLCBleHRyYV9wcm9wX3ZlY3RvcikKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKYGBgCgpHbG9iYWwgbmV0d29yayBwcm9wZXJ0aWVzIGhhdmUgYmVlbiBzYXZlZCBhcyBkYXRhIGZyYW1lcyBmb3IgZnVydGhlciB1c2UuIFRoZXkgd2lsbCBiZSB1c2VkIHRvZ2V0aGVyIHRob3NlIGdlbmVyYXRlZCBmb3IgdGhlIG90aGVyIGRhdGEgc2V0cy4KCiMjIE5vZGUgcHJvcGVydGllcy4KCk5vZGUgcHJvcGVydGllcyBjYW4gYmUgZXh0cmFjdGVkIGZyb20gdGhlIG1pY3JvTmV0UHJvcHMgb2JqZWN0cyBhbmQgc2F2ZWQgZm9yIGZ1cnRoZXIgdXNlLiAgCgpgYGB7ciBleHRyYWN0X25vZGVzLCBkcGkgPSA5Nn0KCiMgZXh0cmFjdCBub2RlIHByb3BlcnRpZXMKIyB1c2luZyBhIGxvb3AgdGFrZXMgc2xpZ2h0bHkgbG9uZ2VyIHRoYXQgdXNpbmcgZnVuY3Rpb25hbHMgYnV0IGhhbmRsZXMgbmFtZXMgYmV0dGVyCiMgbm90ZSB0aGF0IHdoZW4gdXNpbmcgQVNWcyBjb21wYXJpbmcgbm9kZXMgYmV0d2VlbiBkYXRhc2V0cyBkb2VzIG5vdCBtYWtlIG11Y2ggc2Vuc2UKCmlmKGtlZXBfdGltZSkgdGljKCJFeHRyYWN0aW5nIG5vZGUgcHJvcGVydGllcyIpCgpub2RlX3N0YXRzIDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChuZXRfc3RhdHMpKQpmb3IgKGkgaW4gc2VxX2Fsb25nKG5ldF9zdGF0cykpIHsKICBub2RlX3Byb3BlcnRpZXMgPC0gbm9kZV9zdGF0X2xpc3RbW2ldXQogIAogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoImV4dHJhY3Rpbmcgbm9kZSBzdGF0cyBmb3IiLCBuYW1lcyhuZXRfc3RhdHMpW2ldLCJcbiIpCiAgZm9yIChqIGluIHNlcV9hbG9uZyhuZXRfc3RhdHNbW2ldXSkpIHsKICAgIGlmIChjbGFzcyhuZXRfc3RhdHNbW2ldXVtbal1dKSA9PSAibWljcm9OZXRQcm9wcyIpIHsKICAgICAgbm9kZV9zdGF0c1tbaV1dW1tqXV0gPC0gZXh0cmFjdF9ub2RlX3N0YXRzKG5ldF9zdGF0X2xpc3QgPSBuZXRfc3RhdHNbW2ldXVtbal1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGVzdGF0ID0gbm9kZV9wcm9wZXJ0aWVzKQogICAgICBtZXRob2QgPC0gbmFtZXMobmV0X3N0YXRzW1tpXV0pW2pdCiAgICAgIGRhdGFzZXQgPC0gbmFtZXMobmV0X3N0YXRzKVtpXQogICAgICBucm93cyA8LSBucm93KG5vZGVfc3RhdHNbW2ldXVtbal1dKQogICAgICBub2RlX3N0YXRzW1tpXV1bW2pdXSA8LSBiaW5kX2NvbHMoCiAgICAgICAgZGF0YXNldCA9IHJlcChkYXRhc2V0LG5yb3dzKSwKICAgICAgICBtZXRob2QgPSByZXAobWV0aG9kLG5yb3dzKSwKICAgICAgICBub2RlX3N0YXRzW1tpXV1bW2pdXQogICAgICAgICkKICAgIH0gZWxzZSB7CiAgICAgIGNhdCgibm8gbm9kZSBzdGF0cyB0byByZXR1cm4gZm9yIiwKICAgICAgICAgIG5hbWVzKG5ldF9zdGF0cylbaV0sCiAgICAgICAgICBuYW1lcyhub2RlX3N0YXRzW1tpXV0pW2pdLAogICAgICAgICAgIlxuIikKICAgICAgbmV4dAogICAgfQogIH0KICBub2RlX3N0YXRzW1tpXV08LWJpbmRfcm93cyhub2RlX3N0YXRzW1tpXV0pCn0Kbm9kZV9zdGF0c19kZiA8LSBiaW5kX3Jvd3Mobm9kZV9zdGF0cykKIyBwZXJmb3JtIHNvbWUgdGlkeWluZwpub2RlX3N0YXRzX2RmIDwtIG5vZGVfc3RhdHNfZGYgJT4lCiAgdGlkeXI6OnNlcGFyYXRlKGRhdGFzZXQsIGludG8gPSBjKCJTdHVkeSIsICJBY2NuX24iLCAic3VmIiksIHNlcCA9ICJfIiwgcmVtb3ZlID0gRikgJT4lIAogIGRwbHlyOjpzZWxlY3QoLUFjY25fbiwgLXN1ZikgJT4lCiAgbXV0YXRlKGxhYmVsMiA9IGlmX2Vsc2UoIXN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSxzdHJfYyhsYWJlbCwgU3R1ZHksIHNlcCA9ICJfIiksIGxhYmVsKSkKCiMgbGFiZWwyIGlzIG9ubHkgbmVjZXNzYXJ5IHdoZW4gdXNpbmcgQVNWcyBvciBPVFVzLCBub3QgaWYgdGhlcmUgaGFzIGJlZW4gdGF4b25vbWljIGFnZ2xvbWVyYXRpb24KIyBjb25zaWRlciByZW1vdmluZyB0aGUgbXV0YXRlIGluc3RydWN0aW9uCgp3cml0ZV90c3Yobm9kZV9zdGF0c19kZiwgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9ub2Rlc3RhdHNfZGYudHh0IixzZXA9IiIpKQpybShub2RlX3N0YXRzKQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQoKYGBgCgojIyBFZGdlIHByb3BlcnRpZXMuICAKCkVkZ2VzIGFuZCBlZGdlIHByb3BlcnRpZXMgY2FuIGFsc28gYmUgZXh0cmFjdGVkIGZvciBmdXJ0aGVyIHVzZS4gSW1wb3J0YW50IGVkZ2UgcHJvcGVydGllcyBhcmU6CgoqIGVkZ2UgdHlwZSAocG9zaXRpdmUgb3IgbmVnYXRpdmUpICAKCiogZWRnZSB3ZWlnaHQgYW5kIGFzc29jaWF0aW9uIG1lYXN1cmUKCiogZWRnZSBiZXR3ZWVubmVzcyAgCgpJdCBpcyBhbHNvIGNvbnZlbmllbnQgdG8gY29tcGFyZSBlZGdlcyBhbW9uZyBkaWZmZXJlbnQgZ3JhcGhzIChhbmQgdG8gZG8gc28gaXQgbWlnaHQgYmUgY29udmVuaWVudCB0byBtYWtlIHN1cmUgdGhhdCAidG8iIGFuZCAiZnJvbSIgYXJlIGFsd2F5cyBpbiBhbHBoYWJldGljYWwgb3JkZXIsIHdoaWNoIGlzIGFsd2F5cyB0cnVlIHdpdGhpbiB0aGUgc2FtZSBncmFwaCBidXQgbWlnaHQgbm90IGJlIG5lY2Vzc2FyaWx5IHRydWUgYW1vbmcgZGlmZmVyZW50IGdyYXBocykuICAKVGhlIGZvbGxvd2luZyBjaHVuayB3aWxsIChvcHRpb25hbGx5KTogCgoqIGludGVncmF0ZSBub2RlIChjYWxjdWxhdGVkIHdpdGggYG5ldEFuYWx5c2UoKWApIGluIHRoZSB0aWR5Z3JhcGggbGlzdCwgCgoqIGNhbGN1bGF0ZSBlZGdlIGJldHdlZW5uZXNzLCAgCgoqIGNvbXBhcmUgbmV0d29ya3MgKHdpdGhpbiBkYXRhc2V0KSBieSBwcm9kdWNpbmcgYW5kIHNhdmluZyBWZW5uIGRpYWdyYW1zIG9mIHRoZSBlZGdlcwoKRmluYWxseSwgYSBkYXRhIGZyYW1lIHdpdGggYWxsIGVkZ2VzIHdpbGwgYmUgcHJvZHVjZWQgZm9yIGZ1cnRoZXIgdXNlLiAgCgoKYGBge3IgZnVydGhlcl9lZGdlX25vZGVfcHJvcGVydGllcywgZHBpID0gOTZ9CgppZihrZWVwX3RpbWUpIHRpYygiTGlzdCB3aXRoIHRpZHlncmFwaCBvYmplY3RzIGNyZWF0ZWQiKQoKIyBjcmVhdGluZyBhIHRpZHlncmFwaCBvYmplY3QgZm9yIGVhY2ggbmV0CnRpZHlncmFwaF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChNQU5faW5mX3Jlc3VsdHMpKQoKZm9yKGkgaW4gc2VxX2Fsb25nKE1BTl9pbmZfcmVzdWx0cykpewogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoIkNvbnZlcnRpbmcgaW4gdGlkeWdyYXBocyBmb3IiLCBuYW1lcyhNQU5faW5mX3Jlc3VsdHMpW2ldLCJcbiIpCiAgIyB0aGUgd2FybmluZywgaWYgYW55LCBpcyBub3QgdmVyeSBpbmZvcm1hdGl2ZSwgc2hvdWxkIGNvbnNpZGVyIHBhc3NpbmcgbmFtZXMgb2YgZGF0YXNldHMgYW5kIG1ldGhvZHMKICB0aWR5Z3JhcGhfbGlzdFtbaV1dIDwtIE1BTl9pbmZfcmVzdWx0c1tbaV1dICU+JSBtYXAobWljcm9OZXRfdG9fdGlkeWdyYXBoLCBmYWlsX3dfZXJyID0gRiwgdXNlX2Fzc29fbWF0cml4ID0gVCkKfQpuYW1lcyh0aWR5Z3JhcGhfbGlzdCk8LW5hbWVzKE1BTl9pbmZfcmVzdWx0cykKaWYoa2VlcF90aW1lKSB0b2MoKQoKIyBvcHRpb25hbGx5IG1lcmdlIGZ1cnRoZXIgbm9kZSBzdGF0cyAoZGVwZW5kcyBvbiBtZXJnZV9uX3N0YXRzKSAKIyBhbmQgY2FsY3VsYXRlIGVkZ2UgYmV0d2Vlbm5lc3MgKGRlcGVuZHMgb24gY2FsY19lX2JldHcpCiMgSSBhbSB1c2luZyBhIGxvb3AKaWYoa2VlcF90aW1lKSB0aWMoIlN0YXRzIGFkZGVkIHRvIHRpZHlncmFwaHMsIGVkZ2UgZGF0YWZyYW1lIGNyZWF0ZWQiKQp0aWR5Z3JhcGhfbGlzdF93c3RhdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0KSkKIyB0aGUgbGlzdCB3aXRoIHRoZSBlZGdlIGRhdGEgZnJhbWVzCmVkZ2VfbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgodGlkeWdyYXBoX2xpc3QpKQoKZm9yKGkgaW4gc2VxX2Fsb25nKHRpZHlncmFwaF9saXN0X3dzdGF0cykpewogICMgbmVlZCB0byBiZSByZXNldAogIGlubmVyX3RnbCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgodGlkeWdyYXBoX2xpc3RbW2ldXSkpCiAgaW5uZXJfZWwgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0W1tpXV0pKQogIGR0c3QgPC0gbmFtZXModGlkeWdyYXBoX2xpc3QpW2ldCiAgZm9yKGogaW4gc2VxX2Fsb25nKGlubmVyX3RnbCkpewogICAgaWYoaXMudGJsX2dyYXBoKHRpZHlncmFwaF9saXN0W1tpXV1bW2pdXSkpewogICAgbXRoZCA9IG5hbWVzKHRpZHlncmFwaF9saXN0W1tpXV0pW2pdCiAgICBuc3RhdHMgPC0gbm9kZV9zdGF0c19kZiAlPiUgZHBseXI6OmZpbHRlcihkYXRhc2V0ID09IGR0c3QgJiBtZXRob2QgPT0gbXRoZCkKICAgIGlubmVyX3RnbFtbal1dIDwtIG1lcmdlX3N0YXRzKHRnID0gdGlkeWdyYXBoX2xpc3RbW2ldXVtbal1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGVfc3RhdHMgPSBuc3RhdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGViZXR3ID0gY2FsY19lX2JldHcpCiAgICAgIyBleHRyYWN0IGVkZ2UgdGliYmxlCiAgICBpbm5lcl9lbFtbal1dIDwtIGlubmVyX3RnbFtbal1dICU+JSAKICAgICAgYWN0aXZhdGUoZWRnZXMpICU+JSAKICAgICAgYXNfdGliYmxlKCkgJT4lCiAgICAgIG11dGF0ZShtZXRob2QgPSBtdGhkKQogICAgIyBkbyBuYW1pbmcKICAgIG5hbWVzKGlubmVyX3RnbClbal0gPC0gbmFtZXMoaW5uZXJfZWwpW2pdIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0W1tpXV0pW2pdCiAgICB9IGVsc2UgewogICAgICBuZXh0CiAgICB9CiAgfQogIHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dIDwtIGlubmVyX3RnbAogIGVkZ2VfbGlzdFtbaV1dIDwtIGJpbmRfcm93cyhpbm5lcl9lbCkKICBlZGdlX2xpc3RbW2ldXSA8LSBlZGdlX2xpc3RbW2ldXSAlPiUgCiAgICBtdXRhdGUoZGF0YXNldCA9IGR0c3QpICU+JSBkcGx5cjo6c2VsZWN0KGRhdGFzZXQsIG1ldGhvZCwgIShkYXRhc2V0Om1ldGhvZCkpCiAgbmFtZXModGlkeWdyYXBoX2xpc3Rfd3N0YXRzKVtpXSA8LSBuYW1lcyhlZGdlX2xpc3QpW2ldIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0KVtpXQp9CnJtKG5zdGF0cykKIyBidWlsZCBhbmQgZml4IHRoZSBlZGdlIGRmCmVkZ2VfbGlzdF9kZiA8LSBiaW5kX3Jvd3MoZWRnZV9saXN0KQoKZWRnZV9saXN0X2RmIDwtIGVkZ2VfbGlzdF9kZiAlPiUgCiAgbXV0YXRlKG5hbWVfZnJvbV9zb3J0ZWQgPSBpZl9lbHNlKGZyb21fbmFtZSA8IHRvX25hbWUsIGZyb21fbmFtZSwgdG9fbmFtZSksCiAgICAgICAgIG5hbWVfdG9fc29ydGVkID0gaWZfZWxzZShmcm9tX25hbWUgPCB0b19uYW1lLCB0b19uYW1lLCBmcm9tX25hbWUpKSAlPiUKICBtdXRhdGUoZWRnZV9uYW1lID0gc3RyX2MobmFtZV9mcm9tX3NvcnRlZCwgbmFtZV90b19zb3J0ZWQsIHNlcCA9ICItLSIpKQp3cml0ZV90c3YoZWRnZV9saXN0X2RmLCBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX2VkZ2VsaXN0X2RmLnR4dCIsc2VwPSIiKSkKaWYoa2VlcF90aW1lKSB0b2MoKQoKIyBtYWtlIFZlbm4gcGxvdHMKCiMgSSBhbSB1c2luZyBhIGxvb3AKaWYoZG9fVmVubil7CiAgaWYoa2VlcF90aW1lKSB0aWMoIlZlbm4gcGxvdHMgY3JlYXRlZCBhbmQgc2F2ZWQiKQogIFZlbm5fbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgodGlkeWdyYXBoX2xpc3QpKQogIGZvcihpIGluIHNlcV9hbG9uZyh0aWR5Z3JhcGhfbGlzdCkpewogICAgIyBuZWVkIHRvIHNlbGVjdCBvbmx5IGVsZW1lbnRzIG9mIGNsYXNzIHRibF9ncmFwaAogICAgdGJsZ3JwaHMgPC0gbWFwX2xnbCh0aWR5Z3JhcGhfbGlzdFtbaV1dLCBpcy50YmxfZ3JhcGgpCiAgICBuYW1lcyhWZW5uX2xpc3QpIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0KQogICAgaWYobGVuZ3RoKHRpZHlncmFwaF9saXN0W1tpXV1bdGJsZ3JwaHNdKT4xKXsKICAgICAgaW5uZXJfbGlzdCA8LSBtYXAodGlkeWdyYXBoX2xpc3RbW2ldXVt0YmxncnBoc10sIGZ1bmN0aW9uKHgpIGFzX2lkcyhFKHgpKSkKICAgICAgVmVubl90aXRsZSA8LSBuYW1lcyh0aWR5Z3JhcGhfbGlzdClbaV0KICAgICAgVmVubl9maWxlIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwiXyIsVmVubl90aXRsZSwgIl92ZW5ucy50aWZmIixzZXA9IiIpCiAgICAgIG15X2ZpbGwgPC0gKDI6NSlbMTpsZW5ndGgodGlkeWdyYXBoX2xpc3RbW2ldXVt0YmxncnBoc10pXQogICAgICBWZW5uX2xpc3RbW2ldXSA8LSB2ZW5uLmRpYWdyYW0oaW5uZXJfbGlzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBteV9maWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IFZlbm5fZmlsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IDAuMDUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gVmVubl90aXRsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4uY2V4ID0gMS41LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFpbi5mb250ZmFjZSA9ICJib2xkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4uZm9udGZhbWlseSA9ICJzYW5zIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4ucG9zID0gYygwLjUsIDEuMDUpCiAgICAgICAgKQogICAgfQogIH0KICBybShpbm5lcl9saXN0LCBWZW5uX3RpdGxlLCBWZW5uX2ZpbGUpCiAgaWYoa2VlcF90aW1lKSB0b2MoKQp9CgppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKYGBgCkxvb2tpbmcgYXQgdGhlIFZlbm5zIGl0IGNhbiBiZSBzZWVuIHRoYXQgdGhlIHNwYXJzZXN0IG5ldHdvcmtzIGFyZSBhbG1vc3QgYWx3YXlzIHByb2R1Y2VkIGJ5IFNQSUVDLUVBU0kgYW5kIHRoYXQgdGhlIG51bWJlciBvZiBlZGdlcyBzaGFyZWQgYnkgYWxsIG1ldGhvZHMgZm9yIGFueSBnaXZlbiBpcyB2ZXJ5IGxvdy4gCgojIENvbXBhcmluZyBnbG9iYWwgcHJvcGVydGllcy4KCkNvbXBhcmluZyBnbG9iYWwgbmV0d29yayBwcm9wZXJ0aWVzIG1heSBiZSBvZiBhc3Npc3RhbmNlIGluIGV2YWx1YXRpbmcgdGhlIGVmZmVjdCBvZiB0aGUgaW5mZXJlbmNlIG1ldGhvZCBvciBvZiB0aGUgdHlwZSBvZiBzdHVkeS4gQWx0aG91Z2ggTmV0Q29NaSBvZmZlcnMgdmVyeSBlZmZlY3RpdmUgdG9vbHMgdG8gY29tcGFyZSB0d28gbmV0d29ya3MsIGl0IGRvZXMgbm90IGVhc2lseSBnZW5lcmFsaXplIHRvIG1vcmUgdGhhbiAyLTMgbmV0d29ya3MuIEhlcmUsIEkgd2lsbCB1c2UgdGhlIGdsb2JhbF9hbGxfZGYgYW5kIGdsb2JhbF9sY2NfZGYgYW5kIHByb2R1Y2UgZ3JhcGhzIHVzaW5nIFBDQS4gIApfX05vdGVfXyBsaW5lIDEwNTEgc2hvdWxkIGJlIG1hbnVhbGx5IGFkYXB0ZWQgdG8gc2hvdyBhbGwgbG9hZGluZ3MgYW5kIHNjb3JlcwoKYGBge3IgY29tcGFyZV9nbG9iYWwsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiQ29tcGFyaW5nIGdsb2JhbCBwcm9wZXJ0aWVzIikKIyBjcmVhdGUgYSBsYWJlbCBhbmQgZXh0cmFjdCBtYXRyaXggZm9yIHRoZSBhbmFseXNpcwpnbG9iYWxfYWxsX2RmXzIgPC0gZ2xvYmFsX2FsbF9kZiAlPiUKICB0aWR5cjo6c2VwYXJhdGUoZGF0YXNldCwgaW50byA9IGMoInN0dWR5IiwgInR5cGUiLCAib2JqZWN0IiksIHJlbW92ZSA9IEYpICU+JQogIHNlbGVjdCgtdHlwZSwgLW9iamVjdCkgJT4lCiAgbXV0YXRlKG1ldGhvZF9icmllZiA9IGNhc2Vfd2hlbigKICAgIG1ldGhvZCA9PSAic3BpZWNlYXNpIiB+ICJzcGkiLAogICAgbWV0aG9kID09ICJzcHJpbmciIH4gInNwciIsCiAgICBtZXRob2QgPT0gImNjcmVwZSIgfiAiY2NyIiwKICAgIG1ldGhvZCA9PSAic3BhcmNjIiB+ICJzcGEiCiAgKSkgJT4lCiAgdGlkeXI6OnVuaXRlKGxhYmVsLCBzdHVkeSwgbWV0aG9kX2JyaWVmLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEYpCgoKIyBrZWVwIG9ubHkgcmVsZXZhbnQgY29sdW1ucyBhbmQgbWFrZSBhIG1hdHJpeApnbG9iYWxfYWxsX2RmX21hdCA8LSBnbG9iYWxfYWxsX2RmXzIgJT4lIAogIGRwbHlyOjpzZWxlY3QobGFiZWwsIG5Db21wMTpubmVnZWRnZSwgc2FtcGxlcywgQ2hhbzEsIFBpZWxvdV9KLCBhdmVfQkMpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygibGFiZWwiKSAlPiUgYXMubWF0cml4KCkKCiMgb3B0aW9uYWxseSBvYnRhaW4gYSBzY2F0dGVycGxvdCBtYXRyaXgKaWYodmVyYm9zZV9vdXRwdXQpIGdncGFpcnMoYXMuZGF0YS5mcmFtZShnbG9iYWxfYWxsX2RmX21hdCksIHByb2dyZXNzID0gRikKCgojIGxvb2sgYXQgdGhlIG51bWJlciBvZiBjb21wb25lbnRzICh1c2luZyBjb3JyZWxhdGlvbiBtYXRyaXgpCnZhcl90b191c2UgPC0gIShjb2xuYW1lcyhnbG9iYWxfYWxsX2RmX21hdCkgJWluJSBjKCJuY29tcCIsICJ2ZXJ0Q29ubmVjdDEiLCJlZGdlQ29ubmVjdDEiLCJudGF4YSIpKQpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRbLGMoMTo1LDg6MTQsMTc6MTgpXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgoKUENBMSA8LSBwc3ljaDo6cHJpbmNpcGFsKGdsb2JhbF9hbGxfZGZfbWF0WyxjKDE6NSw4OjE0LDE3OjE4KV0sIG5mYWN0b3JzID0gMiwgcm90YXRlID0gInZhcmltYXgiKQpQQ0ExCmJpcGxvdChQQ0ExLCB4bGltLnMgPSBjKC0xLDQpLCB5bGltLnMgPSBjKC0yLDUpLCBtYWluID0gIlBDQSBiaXBsb3QsIGFsbCB2YXJpYWJsZXMiKQpmdWxsbmV0c2NvcmVzIDwtIGNiaW5kKGdsb2JhbF9hbGxfZGZfMixQQ0ExJHNjb3JlcykKIyB0aGUgc2NvcmUgcGxvdApnZ3Bsb3QoZnVsbG5ldHNjb3JlcywgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gImFsbCB2YXJpYWJsZXMiKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoInNwaWVjZWFzaSIgPSAxNiwgInNwcmluZyIgPSAxNywgImNjcmVwZSIgPSAxNSwgInNwYXJjYyIgPSAxOCkpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIodHlwZSA9ICJxdWFsIiwgcGFsZXR0ZSA9ICJQYWlyZWQiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgp2YXJfdG9fdXNlIDwtIGNvbG5hbWVzKGdsb2JhbF9hbGxfZGZfbWF0KSAlaW4lIGMoIm5jb21wMSIsICJtb2R1bGFyaXR5MSIsImRlbnNpdHkxIiwiY2x1c3RDb2VmMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYXZQYXRoMSIsICJwZXAxIiwgIlBpZWxvdV9KIiwgImF2ZV9CQyIsICJubm9kZXMiKQpjYXQoInZhcmlhYmxlcyB0byB1c2U6ICIsIHZhcl90b191c2UsICJcbiIpCgpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRbLHZhcl90b191c2VdLCBmYSA9ICJwYyIsIG4uaXRlciA9IDEwMCkKClBDQTIgPC0gcHJpbmNpcGFsKGdsb2JhbF9hbGxfZGZfbWF0Wyx2YXJfdG9fdXNlXSwgbmZhY3RvcnMgPSAyLCByb3RhdGUgPSAidmFyaW1heCIpClBDQTIKYmlwbG90KFBDQTIpCmZ1bGxuZXRzY29yZXNfMiA8LSBjYmluZChnbG9iYWxfYWxsX2RmXzIsUENBMiRzY29yZXMpCnZhcl9hY2NfUkMxIDwtIHJvdW5kKFBDQTIkVmFjY291bnRlZFs0LDFdLDMpCnZhcl9hY2NfUkMyIDwtIHJvdW5kKFBDQTIkVmFjY291bnRlZFs0LDJdLDMpCiMgdGhlIHNjb3JlIHBsb3QKZ2dwbG90KGZ1bGxuZXRzY29yZXNfMiwgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gInNlbGVjdGVkIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICBsYWJzKHggPSBwYXN0ZSgiUkMxICgiLCB2YXJfYWNjX1JDMSwgIikiLCBzZXAgPSIiKSwKICAgICAgIHggPSBwYXN0ZSgiUkMyICgiLCB2YXJfYWNjX1JDMiwgIikiLCBzZXAgPSIiKSkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKCmdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9QQ0EudGlmZiIsc2VwPSIiKSwgZGV2aWNlID0gInRpZmYiLCB3aWR0aCA9IDYsIAogICAgICAgaGVpZ2h0ID0gNSwgdW5pdHMgPSAiaW4iLCBkcGk9NjAwKQoKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCldpdGggdGhlc2UgbWFueSBkYXRhc2V0cyBhbmQgaW5mZXJlbmNlIG1ldGhvZHMgdGhlIFBDQSBiZWNvbWVzIGRpZmZpY3VsdCB0byB1c2UuIE5vdGUgaG93IHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBuZXR3b3JrIGFyZSwgYXMgZXhwZWN0ZWQsIHNwZWNpZmljIHRvIGFueSBnaXZlbiBpbmZlcmVuY2UgbWV0aG9kcyBhbmQgZGF0YS1zZXQgYW5kIGhvdyBtZXRob2RzIGJhc2VkIG9uIGNvcnJlbGF0aW9uIChzcGFyQ0MgYW5kIENDUkVQRSkgdGVuZCB0byBiZSBjbG9zZXIgYW1vbmcgdGhlbSB0aGFuIHRvIG1ldGhvZHMgYmFzZWQgb24gY29uZGl0aW9uYWwgZGVwZW5kZW5jZS4gCgpUaGUgYW5hbHlzaXMgaXMgcmVwZWF0ZWQgYmVsb3cgb24gdGhlIGxhcmdlc3QgY29ubmVjdGVkIGNvbXBvbmVudCBmb3IgZWFjaCBuZXR3b3JrLiAgCgpgYGB7ciBjb21wYXJlX2dsb2JhbF9MQ0MsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiQ29tcGFyaW5nIGdsb2JhbCBwcm9wZXJ0aWVzIG9uIExDQyIpCiMgY3JlYXRlIGEgbGFiZWwgYW5kIGV4dHJhY3QgbWF0cml4IGZvciB0aGUgYW5hbHlzaXMKZ2xvYmFsX2FsbF9kZl8zIDwtIGdsb2JhbF9sY2NfZGYgJT4lCiAgdGlkeXI6OnNlcGFyYXRlKGRhdGFzZXQsIGludG8gPSBjKCJzdHVkeSIsICJ0eXBlIiwgIm9iamVjdCIpLCByZW1vdmUgPSBGKSAlPiUKICBzZWxlY3QoLXR5cGUsIC1vYmplY3QpICU+JQogIG11dGF0ZShtZXRob2RfYnJpZWYgPSBjYXNlX3doZW4oCiAgICBtZXRob2QgPT0gInNwaWVjZWFzaSIgfiAic3BpIiwKICAgIG1ldGhvZCA9PSAic3ByaW5nIiB+ICJzcHIiLAogICAgbWV0aG9kID09ICJjY3JlcGUiIH4gImNjciIsCiAgICBtZXRob2QgPT0gInNwYXJjYyIgfiAic3BhIgogICkpICU+JQogIHRpZHlyOjp1bml0ZShsYWJlbCwgc3R1ZHksIG9ial90eXBlLCBtZXRob2RfYnJpZWYsIHNlcCA9ICJfIiwgcmVtb3ZlID0gRikKCgojIGtlZXAgb25seSByZWxldmFudCBjb2x1bW5zIGFuZCBtYWtlIG1hdHJpeApnbG9iYWxfYWxsX2RmX21hdF8yIDwtIGdsb2JhbF9hbGxfZGZfMyAlPiUgCiAgZHBseXI6OnNlbGVjdChsYWJlbCwgbGNjU2l6ZTE6bm5lZ2VkZ2UsIHNhbXBsZXMsIENoYW8xLCBQaWVsb3VfSiwgYXZlX0JDKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoImxhYmVsIikgJT4lIGFzLm1hdHJpeCgpCiMgbmVlZCB0byByZW1vdmUgYW4gSW5mIHZhbHVlLCBJIGFtIHJlbW92aW5nIHRoZSByb3cKCiMgb2J0YWluIGEgc2NhdHRlcnBsb3QgbWF0cml4CmlmKHZlcmJvc2Vfb3V0cHV0KSBnZ3BhaXJzKGFzLmRhdGEuZnJhbWUoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyxdKSwgcHJvZ3Jlc3MgPSBGKQoKCiMgbG9vayBhdCB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMgKHVzaW5nIGNvcnJlbGF0aW9uIG1hdHJpeCk6IG11c3QgZXhjbHVkZQojIGF2ZSBwYXRoIGxlbmd0aCBhbmQgY2x1c3RlcmluZyBjb2VmZmljaWVudCB3aGljaCBjb250YWluIEluZiBhbmQgTmFOCgpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyxjKDE6Myw2LDk6MTQsMTc6MTgpXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgoKUENBMV9sY2MgPC0gcHN5Y2g6OnByaW5jaXBhbChnbG9iYWxfYWxsX2RmX21hdF8yWy0zLGMoMTozLDYsOToxNCwxNzoxOCldLCBuZmFjdG9ycyA9IDIsIHJvdGF0ZSA9ICJ2YXJpbWF4IikKUENBMV9sY2MKYmlwbG90KFBDQTFfbGNjLCBtYWluID0gIlBDQSBiaXBsb3QsIGFsbCB2YXJpYWJsZXMiKQpmdWxsbmV0c2NvcmVzX2xjYyA8LSBjYmluZChnbG9iYWxfYWxsX2RmXzNbLTMsXSxQQ0ExX2xjYyRzY29yZXMpCiMgdGhlIHNjb3JlIHBsb3QKZ2dwbG90KGZ1bGxuZXRzY29yZXNfbGNjLCBtYXBwaW5nID0gYWVzKHggPSBSQzEsIHkgPSBSQzIsIHNoYXBlID0gbWV0aG9kLCBjb2xvdXIgPSBzdHVkeSkpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAiYWxsIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnZhcl90b191c2VfbGNjIDwtIGNvbG5hbWVzKGdsb2JhbF9hbGxfZGZfbWF0KSAlaW4lIGMoImxjY1NpemUxIiwgIm1vZHVsYXJpdHkxIiwiZGVuc2l0eTEiLCAicGVwMSIsICJQaWVsb3VfSiIsICJhdmVfQkMiLCAibm5vZGVzIikKY2F0KCJ2YXJpYWJsZXMgdG8gdXNlOiAiLCB2YXJfdG9fdXNlX2xjYywgIlxuIikKCnBzeWNoOjpmYS5wYXJhbGxlbChnbG9iYWxfYWxsX2RmX21hdF8yWy0zLHZhcl90b191c2VfbGNjXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgpQQ0EyX2xjYyA8LSBwcmluY2lwYWwoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyx2YXJfdG9fdXNlX2xjY10sIG5mYWN0b3JzID0gMiwgcm90YXRlID0gInZhcmltYXgiKQpQQ0EyX2xjYwpiaXBsb3QoUENBMl9sY2MpCmZ1bGxuZXRzY29yZXNfMl9sY2MgPC0gY2JpbmQoZ2xvYmFsX2FsbF9kZl8yWy0zLF0sUENBMl9sY2Mkc2NvcmVzKQojIHRoZSBzY29yZSBwbG90CnZhcl9hY2NfUkMxX2xjYyA8LSByb3VuZChQQ0EyJFZhY2NvdW50ZWRbNCwxXSwzKQp2YXJfYWNjX1JDMl9sY2MgPC0gcm91bmQoUENBMiRWYWNjb3VudGVkWzQsMl0sMykKIyB0aGUgc2NvcmUgcGxvdApnZ3Bsb3QoZnVsbG5ldHNjb3Jlc18yX2xjYywgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gInNlbGVjdGVkIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICBsYWJzKHggPSBwYXN0ZSgiUkMxICgiLCB2YXJfYWNjX1JDMV9sY2MsICIpIiwgc2VwID0iIiksCiAgICAgICB4ID0gcGFzdGUoIlJDMiAoIiwgdmFyX2FjY19SQzJfbGNjLCAiKSIsIHNlcCA9IiIpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgpnZ3NhdmUoZmlsZW5hbWUgPSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfUENBX2xjYy50aWZmIixzZXA9IiIpLCBkZXZpY2UgPSAidGlmZiIsIHdpZHRoID0gNiwgCiAgICAgICBoZWlnaHQgPSA1LCB1bml0cyA9ICJpbiIsIGRwaT02MDApCgppZihrZWVwX3RpbWUpIHRvYygpCmBgYAoKVGhlIHJlc3VsdHMgYXJlIHNpbWlsYXIsIGFuZCB0aGVyZSBpcyBub3QgbXVjaCB0byBhZGQuIFdoaWxlIFNQSUVDLUVBU0kgcHJvZHVjZXMgdGhlIG1vc3Qgc3BhcnNlIG5ldHdvcmtzIChhbmQgY2FuIGJlIGEgY2FuZGlkYXRlIGZvciB0aGUgZGV0ZWN0aW9uIG9mIHRoZSBtb3N0IHBhcnNpbW9uaW91cyBpbnRlcmFjdGlvbiBzZXRzKSwgQ0NSRVBFIGFuZCBTcGFyQ0MgYm90aCBtYXkgZGV0ZWN0IGluZGlyZWN0IGludGVyYWN0aW9ucyBhbmQsIGRldGVjdGluZyBjbHVzdGVycyBpbiB0aGlzIG5ldHdvcmtzIG1heSBiZSB1c2VmdWwgdG8gZGV0ZWN0IHN1Yi1iaW9tZXMgd2l0aGluIGEgZ2l2ZW4gc3R1ZHkuCgogCiMgUGxvdHRpbmcgc2VsZWN0ZWQgbmV0d29ya3MuICAKCkkgd2lsbCBub3cgdXNlIHRpZHlncmFwaCBhbmQgZ2dyYXBoIHRvIHBsb3QgdGhlIG5ldHdvcmtzLiBUaGUgdGhpY2tuZXNzIG9mIHRoZSBlZGdlcyBpcyBtYWRlIHByb3BvcnRpb25hbCB0byB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIGFzc29jaWF0aW9uIG1lYXN1cmUuIENvcHJlc2VuY2UgZWRnZXMgYXJlIGluIGdyZWVuLCBtdXR1YWwgZXhjbHVzaW9uIG9uZXMgaW4gcmVkLiBTaXplIG9mIHRoZSBub2RlcyBpcyBtYWRlIHByb3BvcnRpb25hbCB0byB0aGUgdG90YWwgZGVncmVlIChuZWdhdGl2ZSBkZWdyZWUgKyBwb3NpdGl2ZSBkZWdyZWUpLiBUaGUgY29sb3Igb2YgdGhlIG5vZGVzIGlzIGRldGVybWluZWQgYnkgdGhlIHBoeWx1bS4gU29tZSBvZiB0aGlzIG9wdGlvbnMgY2FuIGJlIGFkanVzdGVkIGluIHRoZSBjYWxsIHRvIHRoZSBwbG90X2dncmFwaCgpIGZ1bmN0aW9uIGJlbG93LiBPdGhlcnMgbXVzdCBiZSBhZGp1c3RlZCBpbiB0aGUgY29kZSBvZiB0aGUgZnVuY3Rpb24gKGFsbCBmdW5jdGlvbnMgYXJlIGluIHRoZSBzb3VyY2UgZm9sZGVyKS4gIAoKYGBge3IgcGxvdHRpbmdfbmV0d29ya3MsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiUGxvdHRpbmcgdGhlIG5ldHdvcmtzIHdpdGggZ2dyYXBoIikKIyBjcmVhdGUgYSBsaXN0IG9mIG5ldHdvcmsgcGxvdHMKbmV0cGxvdF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aCh0aWR5Z3JhcGhfbGlzdF93c3RhdHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcodGlkeWdyYXBoX2xpc3Rfd3N0YXRzKSl7CiAgbmV0cGxvdF9saXN0XzIgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dKSkKICBkdHN0IDwtIG5hbWVzKHRpZHlncmFwaF9saXN0X3dzdGF0cylbaV0KICBmb3IoaiBpbiBzZXFfYWxvbmcodGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV0pKXsKICAgIGlmKCFpcy50YmxfZ3JhcGgodGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV1bW2pdXSkpewogICAgICBuZXRwbG90X2xpc3RfMltbal1dIDwtICJubyBwbG90IHRvIHJldHVybiIKICAgICAgbmV4dAogICAgfSBlbHNlIHsKICAgICAgdGcgPC0gdGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV1bW2pdXQogICAgICBtdGhkIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dKVtqXQogICAgICAjIHRoZSBkZWZhdWx0IGZvciB0aGUgYXJndW1lbnQgYzBsMHIgaXMgcGh5bHVtLCBvdGhlcndpc2UKICAgICAgIyB1c2UgYzBsMHIgPSBjbHVzdF9tZW1iCiAgICAgICMgYXJndW1lbnQgbHAgPSAiYm90dG9tIiBpcyB0aGUgZGVmYXVsdDsgInJpZ2h0IiBpcyBhbiBhbHRlcm5hdGl2ZQogICAgICBuZXRwbG90X2xpc3RfMltbal1dIDwtIHBsb3RfZ2dyYXBoKHRpZHlfZ3JhcGggPSB0ZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBtdGhkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSBkdHN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxwID0gInJpZ2h0IikKICAgICAgbmFtZXMobmV0cGxvdF9saXN0XzIpW2pdIDwtIG10aGQKICAgICAgcHJpbnQobmV0cGxvdF9saXN0XzJbW2pdXSkKICAgIH0KICB9CiAgbmV0cGxvdF9saXN0W1tpXV0gPC0gbmV0cGxvdF9saXN0XzIKICBuYW1lcyhuZXRwbG90X2xpc3QpW2ldIDwtIGR0c3QKfQoKcm0odGcsIGR0c3QsIG10aGQsIG5ldHBsb3RfbGlzdF8yKQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCkR1ZSB0byB0aGUgbGFyZ2UgbnVtYmVyIG9mIGludGVyYWN0aW9ucyBpdCBpcyBoYXJkIHRvIGRpc2N1c3MgdGhlIHJlc3VsdHMuIEluIHNvbWUgb2YgdGhlIGdyYXBocyBjbHVzdGVycyBhcmUgY2xlYXJseSBldmlkZW50IChhbmQgY291bGQgYmUgaGlnaGxpZ2h0ZWQgYnkgdXNpbmcgdGhlIGNsdXN0ZXIgaWQgZm9yIGNvbG9ycykuIFRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGdyYXBocyBvYnRhaW5lZCB3aXRoIHRoZSBkaWZmZXJlbnQgbWV0aG9kcyBmb3IgZWFjaCBnaXZlbiBkYXRhc2V0IGlzIGFsc28gZGlmZmVyZW50LiBUaGlzIG1heSBiZSBkdWUgdG8gdGhlIGZhY3QgdGhhdCBib3RoIHRoZSBjb3JyZWxhdGlvbiBiYXNlZCBtZXRob2RzIG1heSBkZXRlY3QgaW5kaXJlY3QgaW50ZXJhY3Rpb25zLiAgCkluIGFkZGl0aW9uLCBnaXZlbiB0aGUgc2NvcGUgb2YgdGhpcyBhbmFseXNpcyAod2l0aCBlbXBoYXNpcyBvbiBub2RlcyBhbmQgZWRnZXMgY29uc2VydmVkIGFjcm9zcyBkaWZmZXJlbnQgc3R1ZGllcykgdGhlcmUgaXMgbGl0dGxlIHBvaW50IGluIGRpc2N1c3NpbmcgaW5kaXZpZHVhbCBuZXR3b3JrcywgYmVjYXVzZSB0aGlzIHdvdWxkIHJlcXVpcmUgZ29pbmcgaW50byBkZXRhaWwgaW4gdGhlIGV4cGVyaW1lbnRhbCBhcHByb2FjaCB1c2VkIGluIGVhY2ggc3R1ZHkuICAKVGhlIGZvbGxvd2luZyBjaHVuayBpcyBkZXNpZ25lZCB0byBjb21wYXJlIG5ldHdvcmsgcGxvdHMgYnVpbHQgd2l0aCBkaWZmZXJlbnQgbWV0aG9kcyBpbiBhIGdyaWQuIFR3byBvZiB0aGUgZGF0YXNldHMgKGNob3NlbiBiZWNhdXNlIHRoZXkgcmV0dXJuZWQgYSBuZXR3b3JrIGZvciBhbGwgaW5mZXJlbmNlIG1ldGhvZHMgYW5kIGJlY2F1c2UgdGhleSBoYWQgbmV0d29ya3Mgb2YgZGlmZmVyZW50IGNvbXBsZXhpdHkgaGF2ZSBiZWVuIGNob3NlbiBoZXJlKS4gSG93ZXZlciwgZHVlIHRvIHRoZSBsYXJnZSBudW1iZXIgb2YgZWxlbWVudHMgaW4gdGhlIGdyYXBoLCBwbG90dGluZyBhbGwgdGhlIGVsZW1lbnRzIChncmFwaCwgbGVnZW5kLCB0aXRsZSkgaXMgZGlmZmljdWx0IGFuZCBtYXkgcmVxdWlyZSBhY3Rpbmcgb24gZ3JhcGhpYyBwYXJhbWV0ZXJzLiAgCgpgYGB7ciBjb21wYXJlX25ldHdvcmtfcGxvdHMsIGRwaSA9IDk2LCBldmFsID0gRn0KaWYoa2VlcF90aW1lKSB0aWMoIlBsb3R0aW5nIGFuZCBzYXZpbmcgbmV0d29ya3MgaW4gYSBncmlkIikKCnAxPC1uZXRwbG90X2xpc3RbWzJdXVtbMV1dCnAyPC1uZXRwbG90X2xpc3RbWzJdXVtbMl1dCnAzPC1uZXRwbG90X2xpc3RbWzJdXVtbM11dCnA0PC1uZXRwbG90X2xpc3RbWzJdXVtbNF1dCmdncmlkXzEgPC0gZWdnOjpnZ2FycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5yb3cgPSAyLCBuY29sID0gMikKZm4gPC0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX1NUMTEwLnRpZmYiLHNlcD0iIikKZ2dzYXZlKGdncmlkXzEsIGZpbGVuYW1lID0gZm4sIGRwaSA9IDYwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSA3KQpnZ3JpZF8xCgpwNTwtbmV0cGxvdF9saXN0W1s5XV1bWzFdXQpwNjwtbmV0cGxvdF9saXN0W1s5XV1bWzJdXQpwNzwtbmV0cGxvdF9saXN0W1s5XV1bWzNdXQpwODwtbmV0cGxvdF9saXN0W1s5XV1bWzRdXQpnZ3JpZF8yIDwtIGVnZzo6Z2dhcnJhbmdlKHA1LCBwNiwgcDcsIHA4LCBucm93ID0gMiwgbmNvbCA9IDIpCmZuIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9TVDEzNi50aWZmIixzZXA9IiIpCmdnc2F2ZShnZ3JpZF8yLCBmaWxlbmFtZSA9IGZuLCBkcGkgPSA2MDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gNykKZ2dyaWRfMgoKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKcm0ocDEsIHAyLCBwMywgcDQsIHA1LCBwNiwgcDcsIHA4KQpgYGAKCgojIE5vZGUgYW5hbHlzaXMuCgpXaGVuIGNvbXBhcmluZyBuZXR3b3JrcyBmcm9tIHNldmVyYWwgc3R1ZGllcyBpdCBtaWdodCBiZSBvZiBpbnRlcmVzdCB0byBjbGFzc2lmeSBub2RlcyBvbiB0aGUgYmFzaXMgb2YgdGhlaXIgbWVhc3VyZXMgb2YgY2VudHJhbGl0eSBhY3Jvc3MgbXVsdGlwbGUgc3R1ZGllczogaXMgdGhlcmUgYSBzdXBlciBodWIgKGkuZS4gYSBub2RlIHdoaWNoIGkgYSBodWIgaW4gc2V2ZXJhbCBuZXR3b3Jrcyk/IEhvdyBhcmUgZGlmZmVyZW50IGNlbnRyYWxpdHkgbWVhc3VyZXMgcmVsYXRlZD8gQXJlIHRoZXJlIG5vZGVzIHdoaWNoIGVuZ2FnZSBtb3JlIG9mdGVuIGluIG5lZ2F0aXZlIGludGVyYWN0aW9ucz8gT2YgY291cnNlIGl0IHdvdWxkIGJlIHBvaW50bGVzcyB0byBjb21wYXJlIGFjcm9zcyBzZXZlcmFsIG1ldGhvZHMgb2YgaW5mZXJlbmNlIG9yLCBnaXZlbiB0aGUgcmVzdWx0cyBhYm92ZSwgYmV0d2VlbiBBU1YgYW5kIEZNQk4gc3R1ZGllcyAod2hlbiBhZ2dyZWdhdGlvbiBhdCB0aGUgZ2VudXMgbGV2ZWwgaXMgdXNlZCkuClRoaXMgc2NyaXB0IHdpbGwgcHJvZHVjZSBhIGZldyBub2RlIHBsb3RzIGFzIGEgcHJvb2Ygb2YgY29uY2VwdC4gQm90aCBTUElFQy1FQVNJIGFuZCBTcGFyQ0Mgd2lsbCBiZSB1c2VkIG9uIGRhdGEgZXh0cmFjdGVkIGZyb20gRk1CTiBhbmQgYWdncmVnYXRlZCBhdCB0aGUgZ2VudXMgbGV2ZWwuCgoKCmBgYHtyIG5vZGVfYW5hbHlzaXNfaHViX2ZyZXEsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiTm9kZSBhbmFseXNpcyIpCgpteW1ldGhvZHMgPC0gYygic3BpZWNlYXNpIiwic3BhcmNjIikKbl90b19sYWJlbCA8LSAyMCAjIG1heCBudW1iZXIgb2Ygbm9kZXMgdG8gbGFiZWwgaW4gdGhlIG5vZGUgcGxvdHMKbm9kZV9hbmFseXNpc19saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChteW1ldGhvZHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcobXltZXRob2RzKSl7CiAgIyBjcmVhdGUgdGhlIGRmcywgb25lIGZvciBzcGllYy1lYXNpIGFuZCBvbmUgZm9yIHNwYXJDQyBhbmQgb25seSBzZWxlY3QgRk1CTgogICMgb25seSBzZWxlY3Qgbm9kZXMgd2l0aCBkZWdyZWU+MAogIEZNQk5fbm9kZV9kZiA8LSBub2RlX3N0YXRzX2RmICU+JQogICAgZHBseXI6OmZpbHRlcihtZXRob2QgPT0gbXltZXRob2RzW2ldKSAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoZGVncmVlPjApICU+JQogICAgbXV0YXRlKFN0dWR5ID0gYXMuZmFjdG9yKFN0dWR5KSwKICAgICAgICAgICByZWxfcG9zX2RlZ3JlZSA9IHBvc19kZWdyZWUgLyBzdW0ocG9zX2RlZ3JlZSkpICU+JQogICAgZHBseXI6OnJlbmFtZSh0bGFiZWwgPSBsYWJlbCkKICAKICAKICAjIHVzZSBhIGxvb3AgdG8gZG8gdGhlIG5vZGUgZGVncmVlIGdyYXBocywgcHJpbnQgdGhlbSBhbmQgcHV0IHRoZW0gaW4gYSBsaXN0CiAgbm9kZV9kZWdyZWVfcGxvdF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aD1ubGV2ZWxzKEZNQk5fbm9kZV9kZiRzdHVkeSkpCiAgCiAgZm9yKGogaW4gc2VxX2Fsb25nKGxldmVscyhGTUJOX25vZGVfZGYkU3R1ZHkpKSl7CiAgICBndGl0bGUgPSBwYXN0ZShsZXZlbHMoRk1CTl9ub2RlX2RmJFN0dWR5KVtqXSwgbXltZXRob2RzW2ldLCBzZXAgPSAiLCAiKQogICAgdGVtcF9kZiA8LSBGTUJOX25vZGVfZGYgJT4lIAogICAgICBkcGx5cjo6ZmlsdGVyKFN0dWR5ID09bGV2ZWxzKEZNQk5fbm9kZV9kZiRTdHVkeSlbal0pICU+JQogICAgICBtdXRhdGUoZGdyID0gcG9zX2RlZ3JlZSArIG5lZ19kZWdyZWUpICU+JQogICAgICBhcnJhbmdlKC1kZ3IpICU+JQogICAgICByb3dpZF90b19jb2x1bW4oKSAlPiUgCiAgICAgIG11dGF0ZSh0b19sYWJlbCA9IGlmX2Vsc2UoKHJvd2lkPD0yMCB8IGlzX2h1YiksIHRsYWJlbCwgTkFfY2hhcmFjdGVyXykpCiAgICAKICAgIGF2ZV9kZWdyZWUgPSBtZWFuKHRlbXBfZGYkZGdyLCBuYS5ybSA9IFQpCiAgICBhdmVfcG9zX2RlZ3JlZSA9IG1lYW4odGVtcF9kZiRwb3NfZGVncmVlLCBuYS5ybSA9IFQpCiAgICAjIGNyZWF0aW5nIHRoZSBwbG90LCBvbmx5IHRoZSBuYW1lcyBvZiB0aGUgdG9wIDIwIG5vZGVzIChpbiB0ZXJtcyBvZiBkZWdyZWUpCiAgICAjIGFyZSBwbG90dGVkLCBodWJzIGFyZSBhbHdheXMgcGxvdHRlZAogICAgZ2dwIDwtIGdncGxvdCgKICAgICAgdGVtcF9kZiwKICAgICAgbWFwcGluZyA9IGFlcygKICAgICAgICB4ID0gcG9zX2RlZ3JlZSwKICAgICAgICB5ID0gZGdyLAogICAgICAgIGxhYmVsID0gc3RyX3RydW5jKGdlbnVzLCAxMiwgc2lkZSA9ICJjZW50ZXIiKQogICAgICApCiAgICApICsKICAgICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbGluZXR5cGUgPSAzLCBzZSA9IEYsIGNvbG9yID0gSSgiYmxhY2siKSwgc2hvdy5sZWdlbmQgPSBGKSArCiAgICAgIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvciA9IHBoeWx1bSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSByZWxBYnVuZGFuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IGJldHdlZW4pKSArCiAgICAgIGdlb21fdGV4dF9yZXBlbChzaG93LmxlZ2VuZCA9IEYsIG1heC5vdmVybGFwcyA9IDIwLCBhbHBoYSA9IEkoMC41KSkgKwogICAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMSkgKwogICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBhdmVfZGVncmVlLCBsaW5ldHlwZSA9IDMsIHNob3cubGVnZW5kID0gRikgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhdmVfcG9zX2RlZ3JlZSwgbGluZXR5cGUgPSAzLCBzaG93LmxlZ2VuZCA9IEYpICsKICAgICAgc2NhbGVfYWxwaGFfY29udGludW91cyhyYW5nZSA9IGMoMC40LCAxKSkgKwogICAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsNikpICsKICAgICAgbGFicygKICAgICAgICB4ID0gInBvc2l0aXZlIGRlZ3JlZSIsCiAgICAgICAgeSA9ICJkZWdyZWUiLAogICAgICAgIHNpemUgPSAicmVsYXRpdmUgYWJ1bmRhbmNlIiwKICAgICAgICBhbHBoYSA9ICJiZXR3ZWVubmVzcyIsCiAgICAgICAgdGl0bGUgPSBndGl0bGUKICAgICAgKSArCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAgIAogICAgcHJpbnQoZ2dwKQogICAgbm9kZV9kZWdyZWVfcGxvdF9saXN0W1tqXV0gPC0gZ2dwCiAgICBuYW1lcyhub2RlX2RlZ3JlZV9wbG90X2xpc3RbW2pdXSkgPC0gZ3RpdGxlCiAgfQogIGZpbGVfbmFtZSA8LSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfIiwgbXltZXRob2RzW2ldLCAiX25vZGVwbG90cy5SZGF0YSIsc2VwPSIiKQogIHNhdmUobm9kZV9kZWdyZWVfcGxvdF9saXN0LCBmaWxlID0gZmlsZV9uYW1lKQogIAogICMgY2FsY3VsYXRlIGZyZXF1ZW5jeSBmb3IgaHVicyAobWVhbmluZ2Z1bCBvbmx5IGlmIHlvdSBoYXZlIG1hbnkgdGF4YSBhbmQgdGhlIHBlcmNlbnRpbGUgZm9yIGh1YiBkZXRlY3Rpb24gaXMgbG93LCBzYXkgMC43NS0wLjkwKQogIG5fZGF0YXNldHMgPC0gZHBseXI6Om5fZGlzdGluY3QoRk1CTl9ub2RlX2RmJGRhdGFzZXQpCiAgRk1CTl9ub2RlX2RmX2h1YnMgPC0gRk1CTl9ub2RlX2RmICU+JQogICAgZHBseXI6OmZpbHRlcihpc19odWIgPT0gVCkgJT4lCiAgICBncm91cF9ieSh0bGFiZWwsIHBoeWx1bSwgY2xhc3MpICU+JQogICAgc3VtbWFyaXNlKGh1Yl9mcmVxID0gbigpL25fZGF0YXNldHMpICU+JQogICAgYXJyYW5nZSgtaHViX2ZyZXEpCiAgY2F0KCJtZXRob2QiLCBteW1ldGhvZHNbaV0sICJodWIgZnJlcXVlbmN5IiwgIlxuIikKICBoZWFkKEZNQk5fbm9kZV9kZl9odWJzLCAyMCkKICAjIHNhdmUgZWxlbWVudHMgaW4gdGhlIGxpc3QKbm9kZV9hbmFseXNpc19saXN0W1tpXV0gPC0gbGlzdCgKICBub2RlX2RmID0gRk1CTl9ub2RlX2RmLAogIHBsb3RfbGlzdCA9IG5vZGVfZGVncmVlX3Bsb3RfbGlzdCwKICBub2RlX2RmX2h1YnMgPSBGTUJOX25vZGVfZGZfaHVicwopCm5hbWVzKG5vZGVfYW5hbHlzaXNfbGlzdClbaV08LSBteW1ldGhvZHNbaV0KICAKfQoKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKcm0oRk1CTl9ub2RlX2RmLCBub2RlX2RlZ3JlZV9wbG90X2xpc3QsIHRlbXBfZGYsIEZNQk5fbm9kZV9kZl9odWJzKQpgYGAKClRoZSBub2RlIHBsb3RzIHByb3ZpZGUgYSBudW1iZXIgb2YgdmlzdWFsIGN1ZXM6CgoqIHRoZSBob3Jpem9udGFsIGFuZCB2ZXJ0aWNhbCBkb3R0ZWQgbGluZXMgc2hvdyB0aGUgYXZlcmFnZSB2YWx1ZSBmb3IgZGVncmVlIGFuZCBwb3NpdGl2ZSBkZWdyZWUgYW5kIG1heSBoZWxwIGluIGlkZW50aWZ5aW5nIG5vZGVzIHdob3NlIHZhbHVlcyBhcmUgZmFyIGZyb20gdGhlIG1lYW47ICAKCiogdGhlIGRpYWdvbmFsIGNvbnRpbnVvdXMgbGluZSBjb3JyZXNwb25kcyB0byBwb2ludHMgZm9yIHdoaWNoIHBvc2l0aXZlIGRlZ3JlZSBhbmQgZGVncmVlIGFyZSBlcXVhbDsgcG9pbnRzIGFib3ZlIHRoZSBsaW5lIGhhdmUgYXQgbGVhc3QgMSBuZWdhdGl2ZSBpbnRlcmFjdGlvbgoKKiB0aGUgZGlhZ29uYWwgZG90dGVkIGxpbmUgaXMgdGhlIGxpbmVhciByZWdyZXNzaW9uIGxpbmUgZm9yIGRlZ3JlZSBhcyBhIGZ1bmN0aW9uIG9mIHBvc2l0aXZlIGRlZ3JlZTsgaXRzIHBvc2l0aW9uIGFuZCBzbG9wZSByZWxhdGl2ZSB0byB0aGUgcHJldmlvdXMgbGluZSBpbmRpY2F0ZXMgb24gYXZlcmFnZSBob3cgbXVjaCB0aGUgcmF0aW8gYmV0d2VlbiB0b3RhbCBkZWdyZWUgYW5kIHBvc2l0aXZlIGRlZ3JlZSBjaGFuZ2VzIGFzIGEgZnVuY3Rpb24gb2YgZGVncmVlOyBkYXRhIHBvaW50cyB3aGljaCBhcmUgZmFyIGZyb20gdGhpcyBsaW5lIGFsc28gaGF2ZSBhbiB1bnVzdWFsIHJhdGlvIGJldHdlZW4gdGhlIHR3byB2YWx1ZXMgY29tcGFyZWQgdG8gdGhlIG90aGVyIG5vZGVzIGluIHRoZSBzYW1lIGRhdGFzZXQuICAKCiogbmVnYXRpdmUgaHVicyB3aWxsIGJlIGxvY2F0ZWQgY2xvc2UgdG8gdGhlIHVwcGVyIGxlZnQgY29ybmVyOyBwb3NpdGl2ZSBodWJzIHdpbGwgYmUgY2xvc2VyIHRvIHRoZSByaWdodCBvZiB0aGUgcGxvdC4gIAoKRXZlbiB3aXRoIFNQSUVDLUVBU0kgdGhlIG51bWJlciBvZiBwbG90dGVkIG5vZGVzIGlzIGhpZ2ggZm9yIHNvbWUgc3R1ZGllcyAoYnV0IHZlcnkgbG93IGZvciBvdGhlcnMpLCBhbmQgb25lIG1heSBjb25zaWRlciBwbG90dGluZyBhIGdpdmVuIG51bWJlciBvZiBub2RlcyAodGhlIG9uZXMgd2l0aCB0b3AgZGVncmVlPyB0b3AgZWlnZW52ZWN0b3IgY2VudHJhbGl0eT8sIHNvbWUgY29tYmluYXRpb24gb2YgYm90aCkgdG8gc2ltcGxpZnkgdGhlIHBsb3QuICAKU2FpZCB0aGF0LCBhbHRob3VnaCBiZXR3ZWVubmVzcyBjZW50cmFsaXR5IG1heSBiZSBpbnRlcmVzdGluZyB0byBpZGVudGlmeSAiYm90dGxlbmVjayIgdGF4YSwgbG9va2luZyBhdCBiZXR3ZWVubmVzcyBpcyBkaWZmaWN1bHQgKHRoZSBzaGFkZXMgb2YgYWxwaGEgPSB0cmFuc3BhcmVuY3kgYXJlIHZpc3VhbGx5IGRpZmZpY3VsdCB0byBzZXBhcmF0ZSksIGJ1dCBvdGhlcndpc2UgZGVncmVlLCBwb3NpdGl2ZSBkZWdyZWUgYW5kIGFidW5kYW5jZSBhcmUgYWxsIGVhc3kgdG8gdmlzdWFsaXNlLiAgCgojIEVkZ2UgYW5hbHlzaXMuICAKCkVkZ2UgYW5hbHlzaXMgb25seSBtYWtlcyBzZW5zZSBvbmNlIG9uZSBoYXMgc2V0dGxlZCBmb3IgbWVhbmluZ2Z1bCBxdWVzdGlvbnMgYW5kIHNlbGVjdGVkIGEgbGFyZ2UgYW5kIGRpdmVyc2UgZW5vdWdoIHNldCBvZiBzdHVkaWVzLgpRdWVzdGlvbnMgd2hpY2ggbWlnaHQgYmUgcmVsZXZhbnQ6CgoqIGhvdyBzdGFibGUgaXMgYW4gZWRnZT8KCiAgKyB3aXRoaW4gYSBzdHVkeSBhY3Jvc3MgbWV0aG9kcwogIAogICsgYWNyb3NzIHN0dWRpZXMgd2l0aCBhIGdpdmVuIG1ldGhvZAogIAoqIGdpdmVuIGFuIGluZmVyZW5jZSBtZXRob2QsIGNhbiBvbmUgYXNzZW1ibGUgYSBjb25zZW5zdXMgbmV0d29yayB1c2luZyBhcyB3ZWlnaHRzIHRoZSBudW1iZXIgb2YgdGltZXMgYSBnaXZlbiBlZGdlIG9jY3Vycz8gVGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlPwoKKiBob3cgaW1wb3J0YW50IGlzIGFuIGVkZ2UgKGFzIG1lYXN1cmVkIGJ5IGVkZ2UgYmV0d2Vlbm5lc3MpPyBPbmUgbXVzdCBjaG9vc2UgYSBtZWFuaW5nZnVsIGNvbnRleHQgZm9yIHRoaXMKCiogYXJlIGNvLW9jY3VycmVuY2UgcmVsYXRpb25zaGlwcyBtb3JlIGZyZXF1ZW50IGFtb25nIG1lbWJlcnMgb2YgdGhlIHNhbWUgY2xhc3Mgb3IgZmFtaWx5IChhbmQgdGhlIHJldmVyc2UgZm9yIG11dHVhbCBleGNsdXNpb24pPyBUaGlzIG9ubHkgbWFrZXMgc2Vuc2Ugd2l0aGluIGEgZ2l2ZW4gbWV0aG9kIGFuZCBtdXN0IGJlIGNvcnJlY3RlZCBieSB0aGUgZnJlcXVlbmN5IG9mIGVjaCBnaXZlbiBsYXJnZXIgdGF4b24uCgoqIGdpdmVuIGEgKHBvc3NpYmx5IGltcG9ydGFudCkgbWljcm9vcmdhbmlzbSwgd2l0aCB3aGljaCBtaWNyb29yZ2FuaXNtcyBpcyBpdCBtb3N0IGZyZXF1ZW50bHkgYXNzb2NpYXRlZCB3aXRoIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIGFzc29jaWF0aW9ucz8gVGhpcyBvbmx5IG1ha2VzIHNlbnNlIHdpdGhpbiBhIGdpdmVuIG1ldGhvZCBhbmQgb25jZSBvbmUgaGFzIGFuYWx5emVkIGEgbGFyZ2UgbnVtYmVyIG9mIHN0dWRpZXMKCiogZm9yIG1vZGVyYXRlbHkgbGFyZ2UgbmV0d29ya3MsIHdoaWNoIGlzIHRoZSBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBvZiBlZGdlIGJldHdlZW5uZXNzPyBEb2VzIHRoaXMgaG9sZCBhbnkgaW1wb3J0YW5jZSBpbiB0ZXJtcyBvZiBzdHJ1Y3R1cmUgb2YgdGhlIG5ldHdvcmtzPwoKYGBge3IgZWRnZV9hbmFseXNpc19iZXR3ZWVuZXNzLCBkcGkgPSA5Nn0KaWYoa2VlcF90aW1lKSB0aWMoIkVkZ2UgYW5hbHlzaXMiKQojIHRoaXMgY2FsY3VsYXRlcyB0aGUgZW1waXJpY2FsIHF1YW50aWxlIG9mIGVkZ2UgYmV0d2Vlbm5lc3MKIyBieSBkYXRhc2V0IGFuZCBtZXRob2Q6IEkgc3VwcG9zZSBvbmUgY2FuIHRoZW4gc2VsZWN0IHRob3NlID4wLjk1CiMgdG8gZ2V0IGEgc29ydCBvZiBodWJuZXNzIGZvciBlZGdlcywgYnV0IGl0IG1ha2VzIHNlbnNlIG9ubHkgZm9yIAojIGxhcmdlIG51bWJlciBvZiBlZGdlcwplZGdlX2xpc3RfZGYgPC0gZWRnZV9saXN0X2RmICU+JSBncm91cF9ieShkYXRhc2V0LCBtZXRob2QpICU+JQogIG11dGF0ZShlYnEgPSBlY2RmKGVkZ2VfYmV0dykoZWRnZV9iZXR3KSkgJT4lIHVuZ3JvdXAoKQoKIyBjYWxjdWxhdGUgdGhlIGZyZXF1ZW5jeSBvZiBlZGdlcywgYnkgc3R1ZHkgYW5kIHR5cGUgYW5kIAojIG1lZGlhbiB2YWx1ZSBhbmQgSVFSIGZvciBJUVIKIyB0aGlzIG9ubHkgY2hlY2tzIGZvciBjb25zZXJ2ZWQgZWRnZXMgYWNyb3NzIG1ldGhvZHMKZWRnZV9mcmVxX2J5c3R1ZHkgPC0gZWRnZV9saXN0X2RmICU+JSAKICBncm91cF9ieShkYXRhc2V0LCBhc3NvX3R5cGUsIGVkZ2VfbmFtZSkgJT4lIAogIHN1bW1hcmlzZShuID0gbigpLAogICAgICAgICAgICBtZWRfZWJxID0gbWVkaWFuKGVicSksCiAgICAgICAgICAgIGlxcl9lYnEgPSBJUVIoZWJxKSkgJT4lCiAgYXJyYW5nZSgtbiwgLW1lZF9lYnEsICkgJT4lIAogIHVuZ3JvdXAoKQoKIyBJIGFtIG5vdyBnZW5lcmF0aW5nIGEgcGxvdCBtYXkgYmUgd2l0aCB0aGUgdG9wIDUwIGVkZ2VzLCBvbmx5IGZvciBGTUJOIHN0dWRpZXMKZWRnZV9mcmVxX2J5c3R1ZHlfRk1CTiA8LSBlZGdlX2ZyZXFfYnlzdHVkeSAlPiUKICBkcGx5cjo6ZmlsdGVyKHN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSkgJT4lCiAgYXJyYW5nZSgtbiwgLW1lZF9lYnEpCgojIGxldCdzIHRyeSBhIHBsb3QgKGdpdmVzIGFuIGVtcGhhc2lzIHRvIG1vc3Qgc3RhYmxlIGVkZ2VzKQpzbGljZV90b19wbG90IDwtIHNsaWNlKGVkZ2VfZnJlcV9ieXN0dWR5X0ZNQk4sIDE6NTApICU+JQogICAgbXV0YXRlKGVkZ2VfbmFtZSA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKGVkZ2VfbmFtZSwgbWVkX2VicSkpIAoKZ2dwbG90KHNsaWNlX3RvX3Bsb3QsIAogICAgICAgbWFwcGluZyA9IGFlcyh4ID0gZWRnZV9uYW1lLCB5ID0gbWVkX2VicSwgc2l6ZSA9IG4sIGNvbG9yID0gYXNzb190eXBlKSkgKwogIGdlb21fcG9pbnQoKSArIAogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gImVkZ2UiLCB5ID0gImVkZ2UgYmV0d2Vlbm5lc3MgcXVhbnRpbGUiLAogICAgICBzaXplID0gImVkZ2UgZnJlcS4iKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImdyZWVuIiwicmVkIikpCgoKIyBhbm90aGVyIHdheSB0byBzZWUgaXQsIGJhc2VkIG9uIHN0YWJpbGl0eQpzbGljZV90b19wbG90XzIgPC0gc2xpY2UoZWRnZV9mcmVxX2J5c3R1ZHlfRk1CTiwgMTo1MCkgJT4lCiAgICBtdXRhdGUoZWRnZV9uYW1lID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoZWRnZV9uYW1lLCBuKSkKCmdncGxvdChzbGljZV90b19wbG90XzIsIAogICAgICAgbWFwcGluZyA9IGFlcyh4ID0gZWRnZV9uYW1lLCB5ID0gbiwgc2l6ZSA9IG1lZF9lYnEsIGNvbG9yID0gYXNzb190eXBlKSkgKwogIGdlb21fcG9pbnQoKSArIAogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gImVkZ2UiLCB5ID0gImVkZ2UgZnJlcS4iLAogICAgICBzaXplID0gc3RyX3dyYXAoImVkZ2UgYmV0d2Vlbm5lc3MgcXVhbnRpbGUiLCAxNSkpICsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiZ3JlZW4iLCJyZWQiKSkKCmZuIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9lZGdlX24udGlmZiIsc2VwPSIiKQpnZ3NhdmUoZmlsZW5hbWUgPSBmbiwgZHBpID0gNjAwLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA3KQpgYGAKCk1hbnkgb2YgdGhlIG1vc3Qgc3RhYmxlIGVkZ2VzIGluY2x1ZGUgZ3V0IGJhY3RlcmlhIGFuZCBwcm9iYWJseSByZXByZXNlbnQgY2x1c3RlciBvZiBpbnRlcmFjdGlvbnMgZnJvbSB0aGlzIGVudmlyb25tZW50IHdoaWNoIGFyZSBjYXJyaWVkIG92ZXIgdG8gbWlsayBhbmQgdGhlbiwgcG9zc2libHksIGNoZWVzZSAoc29tZSBzdHVkaWVzIGFuYWx5emVkIGhlcmUgaW5jbHVkZSBhbHNvIGVudmlyb25tZW50YWwgc2FtcGxlcywgbGlrZSBmZWNlcykuICAKVGhlIGFuYWx5c2lzIGhlcmUgd2FzIGNhcnJpZWQgb3V0IGJ5IGdyb3BpbmcgYnkgZGF0YXNldCBhbmQgYXNzb2NpYXRpb24gdHlwZSwgdGhlcmVmb3JlIHRoZSBmcmVxdWVuY3kgaXMgYnkgZGF0YXNldCBhbmQgYSBmcmVxdWVuY3kgb2YgNCBpbmRpY2F0ZXMgdGhhdCBhIGdpdmVuIGVkZ2Ugd2FzIGRldGVjdGVkIGJ5IGFsbCA0IG1ldGhvZHMgaW4gYSBzaW5nbGUgZGF0YXNldC4gVGhlIGNvZGUgY2FuIGJlIGVhc2lseSBtb2RpZmllZCB0byBkZXRlY3QgZWRnZXMgd2hpY2ggYXJlIGNvbnNlcnZlZCBhY3Jvc3MgZGlmZmVyZW50IHN0dWRpZXMgYW5kIGRpZmZlcmVudCBtZXRob2RzLgpJIHdpbGwgbm93IHRyeSB0byBlc3RpbWF0ZSB0aGUgdGF4b25vbWljIGFzc29ydGF0aXZpdHkuIFRvIGRvIHRoaXMsIHRoZSBvZGRzIHJhdGlvIChPUikgZm9yIGNvcHJlc2VuY2UgbGlua3MgKGdpdmVuIHRoYXQgdGhlIG5vZGVzIGJlbG9uZyB0byB0aGUgc2FtZSBmYW1pbHkpIGFuZCBtdXR1YWwgZXhsdXNpb24gbGlua3MgKGdpdmVuIHRoYXQgdGhlIG5vZGVzIGJlbG9uZyB0byBkaWZmZXJlbnQgZmFtaWxpZXMpIGlzIGNhbGN1bGF0ZWQgYW5kIHRoZSBzaWduaWZpY2FuY2UgaXMgZXZhbHVhdGVkIHVzaW5nIGBlcGkuMmJ5MigpYCBmdW5jdGlvbi4gIAoKCmBgYHtyIGVkZ2VfYW5hbHlzaXNfYXNzb3J0YXRpdml0eSwgZHBpID0gOTZ9CiMgc2FtZSwgYnkgbWV0aG9kIGFuZCBhc3NvdHlwZSwgb25seSBmb3IgRk1CTgojIG9ubHkgbWVhbmluZ2Z1bCBmb3IgaGlnaCBudW1iZXIgb2Ygc3R1ZGllcwplZGdlX2ZyZXFfYnltZXRob2QgPC0gZWRnZV9saXN0X2RmICU+JSAKICBkcGx5cjo6ZmlsdGVyKHN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBhc3NvX3R5cGUpICU+JSAKICBjb3VudChlZGdlX25hbWUpICU+JQogIGFycmFuZ2UobWV0aG9kLCBhc3NvX3R5cGUsIC1uKQoKIyBUaGUgZm9sbG93aW5nIHNlY3Rpb24gZXZhbHVhdGVzIHRheG9ub21pYyBhc3NvcnRhdGl2aXR5IChpLmUuIGV2YWx1YXRlcyBpZiAKIyBjb3ByZXNlbmNlIGFzc29jaWF0aW9ucyBhcmUgbW9yZSBmcmVxdWVudCBhbW9uZyBtZW1iZXJzIG9mIHRoZSBzYW1lIAojIGZhbWlseSwgb3JkZXIgb3IgY2xhc3MpLiBUaGUgb2RkcyByYXRpbyBvZiBjb3ByZXNlbmNlIHJlbGF0aW9uc2hpcHMgd2l0aGluCiMgdGhlIHNhbWUgZmFtaWx5IGlzIGNhbGN1bGF0ZWQgdXNpbmcgZXBpUjo6ZXBpLjJieTIoKQoKIyBmaXJzdCwgYWRkIHRheG9ub215IHRvIHRoZSAKdW5pcXVlX3RheCA8LSBub2RlX3N0YXRzX2RmICU+JSAKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBkb21haW46c3BlY2llcykgJT4lCiAgZGlzdGluY3QoKQoKZWRnZV9saXN0X2RmX3d0YXhhIDwtIGVkZ2VfbGlzdF9kZgplZGdlX2xpc3RfZGZfd3RheGEgPC0gbGVmdF9qb2luKGVkZ2VfbGlzdF9kZl93dGF4YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh1bmlxdWVfdGF4LCBsYWJlbCwgY2xhc3MsIG9yZGVyLCBmYW1pbHkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoImZyb21fbmFtZSIgPSAibGFiZWwiKSkgJT4lCiAgZHBseXI6OnJlbmFtZShmcm9tX2NsYXNzID0gY2xhc3MsIGZyb21fb3JkZXIgPSBvcmRlciwgZnJvbV9mYW1pbHkgPSBmYW1pbHkpCmVkZ2VfbGlzdF9kZl93dGF4YSA8LSBsZWZ0X2pvaW4oZWRnZV9saXN0X2RmX3d0YXhhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHVuaXF1ZV90YXgsIGxhYmVsLCBjbGFzcywgb3JkZXIsIGZhbWlseSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygidG9fbmFtZSIgPSAibGFiZWwiKSkgJT4lCiAgZHBseXI6OnJlbmFtZSh0b19jbGFzcyA9IGNsYXNzLCB0b19vcmRlciA9IG9yZGVyLCB0b19mYW1pbHkgPSBmYW1pbHkpCiMgY2hlY2sgaWYgc2FtZSBmYW1pbHkgb3Igc2FtZSBjbGFzcwplZGdlX2xpc3RfZGZfd3RheGEgPC0gZWRnZV9saXN0X2RmX3d0YXhhICU+JQogIG11dGF0ZShzYW1lX2NsYXNzID0gaWZfZWxzZShmcm9tX2NsYXNzID09IHRvX2NsYXNzLCBULCBGKSwKICAgICAgICAgc2FtZV9vcmRlciA9IGlmX2Vsc2UoZnJvbV9vcmRlciA9PSB0b19vcmRlciwgVCwgRiksCiAgICAgICAgIHNhbWVfZmFtaWx5ID0gaWZfZWxzZShmcm9tX2ZhbWlseSA9PSB0b19mYW1pbHksIFQsIEYpCiAgICAgICAgICkKCiMgdXNlIGEgbG9vcCB0byBjYWxjdWxhdGUgb2RkcyByYXRpb3MgYW5kIHByb2JhYmlsaXRpZXMKIyBnZXQgdGhlIG51bWJlciBvZiBzdHVkaWVzCgplZGdlX2xpc3RfZGZfd3RheGEgPC0gZWRnZV9saXN0X2RmX3d0YXhhICU+JSAKICBzZXBhcmF0ZShkYXRhc2V0LCBpbnRvID0gYygic3R1ZHkiLCAiY29sMiIsICJjb2wzIiksIHJlbW92ZSA9IEYpICU+JQogIHNlbGVjdCgtY29sMiwgLWNvbDMpICU+JQogIG11dGF0ZShzdHVkeSA9IGFzLmZhY3RvcihzdHVkeSksCiAgICAgICAgIHNmID0gZmFjdG9yKHNhbWVfZmFtaWx5LCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpLAogICAgICAgICBzbyA9IGZhY3RvcihzYW1lX29yZGVyLCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpLAogICAgICAgICBzYyA9IGZhY3RvcihzYW1lX2NsYXNzLCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpCiAgKQoKYXNzb3J0X3Jlc3VsdHNfbGlzdCA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbmxldmVscyhlZGdlX2xpc3RfZGZfd3RheGEkc3R1ZHkpKQp0YXhvX2xldmVsX3NlbGVjdGlvbiA8LSAiZmFtaWx5Igpmb3IoaSBpbiBzZXFfYWxvbmcobGV2ZWxzKGVkZ2VfbGlzdF9kZl93dGF4YSRzdHVkeSkpKXsKICBpbnB1dF9kZl9zdHVkeSA8LSBlZGdlX2xpc3RfZGZfd3RheGEgJT4lIGRwbHlyOjpmaWx0ZXIoc3R1ZHkgPT0gbGV2ZWxzKHN0dWR5KVtpXSkgJT4lCiAgICBtdXRhdGUobWV0aG9kID0gYXMuZmFjdG9yKG1ldGhvZCkpCiAgaW5uZXJfcmVzdWx0X2xpc3QgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IG5sZXZlbHMoaW5wdXRfZGZfc3R1ZHkkbWV0aG9kKSkKICBmb3IoaiBpbiBzZXFfYWxvbmcobGV2ZWxzKGlucHV0X2RmX3N0dWR5JG1ldGhvZCkpKXsKICAgaW5wdXRfZGZfbWV0aG9kIDwtIGlucHV0X2RmX3N0dWR5ICU+JSAKICAgICAgZHBseXI6OmZpbHRlcihtZXRob2QgPT0gbGV2ZWxzKG1ldGhvZClbal0pCiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJcblByb2Nlc3NpbmciLCBsZXZlbHMoZWRnZV9saXN0X2RmX3d0YXhhJHN0dWR5KVtpXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZXRob2QiLCBsZXZlbHMoaW5wdXRfZGZfbWV0aG9kJG1ldGhvZClbal0sIlxuIikKICAgIGlubmVyX3Jlc3VsdF9saXN0W1tqXV08LXRyeShvZGRzX3JhdGlvKGlucHV0X2RmX21ldGhvZCwgdGF4b19sZXZlbCA9IHRheG9fbGV2ZWxfc2VsZWN0aW9uKSkKICAgIGlmKHZlcmJvc2Vfb3V0cHV0ICYgY2xhc3MoaW5uZXJfcmVzdWx0X2xpc3RbW2pdXSkgPT0gImRhdGEuZnJhbWUiKSB7CiAgICAgIGNhdCgiXG5TdWNjZXNzISBPZGRzIHJhdGlvIGVzdGltYXRlZCIpCiAgICB9IGVsc2UgewogICAgICBjYXQoIlxuRmFpbDogdW5hYmxlIHRvIHJldHVybiB0ZXN0IHJlc3VsdHMhIikKICAgIH0KICAgIG5hbWVzKGlubmVyX3Jlc3VsdF9saXN0KVtqXTwtIGxldmVscyhpbnB1dF9kZl9tZXRob2QkbWV0aG9kKVtqXQogIH0KICBhc3NvcnRfcmVzdWx0c19saXN0W1tpXV0gPC0gaW5uZXJfcmVzdWx0X2xpc3QKICBuYW1lcyhhc3NvcnRfcmVzdWx0c19saXN0KVtpXSA8LSBsZXZlbHMoZWRnZV9saXN0X2RmX3d0YXhhJHN0dWR5KVtpXQp9CiMgY2xlYW4tdXAgdGhlIGxpc3QgYW5kIHB1dCB0b2dldGhlciB0aGUgZGF0YSBmcmFtZXMKCmRmX2xpc3QgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChhc3NvcnRfcmVzdWx0c19saXN0KSkKZm9yKGkgaW4gc2VxX2Fsb25nKGFzc29ydF9yZXN1bHRzX2xpc3QpKXsKICBkZl9saXN0W1tpXV0gPC0gZGZfcmV0dXJuKGFzc29ydF9yZXN1bHRzX2xpc3RbW2ldXSkgCiAgbmFtZXMoZGZfbGlzdClbaV08LW5hbWVzKGFzc29ydF9yZXN1bHRzX2xpc3QpW2ldCn0KCmFzc29ydF9yZXN1bHRzX2RmIDwtIGJpbmRfcm93cyhkZl9saXN0LCAuaWQgPSAic3R1ZHkiKQoKIyBjcmVhdGUgZHVtbXkgT1Jlc3QgdmFsdWVzIGJ5IHJlcGxhY2luZyBJbmYKYXNzb3J0X3Jlc3VsdHNfZGYgPC0gYXNzb3J0X3Jlc3VsdHNfZGYgJT4lCiAgbXV0YXRlKE9SX2VzdF93ZHVtbXkgPSBpZl9lbHNlKE9SX2VzdD09SW5mLCA5OSwgT1JfZXN0KSkgJT4lCiAgbXV0YXRlKGxPUl9lc3Rfd2R1bW15ID0gbG9nKE9SX2VzdF93ZHVtbXkpLAogICAgICAgICBzaWduaWZpY2FudCA9IGlmX2Vsc2UoT1JfcC52YWx1ZS4xcyA8PTAuMDUsIFQsIEYpKSAKCiMgcGxvdCwgc3R1ZGllcyBhcmUgcG9vbGVkCmdncGxvdChhc3NvcnRfcmVzdWx0c19kZiwgbWFwcGluZyA9IGFlcyh4ID0gYXNzb3J0X3Rlc3QsIHkgPSBsT1JfZXN0X3dkdW1teSwgY29sb3VyID0gc2lnbmlmaWNhbnQpKSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kKSArCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjIpICsKICBsYWJzKHkgPSAibG9nKG9kZHMgcmF0aW8pIikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIHRhYnVsYXRpbmcKeHRhYnMofiBtZXRob2QgKyBzaWduaWZpY2FudCArYXNzb3J0X3Rlc3QgLCBkYXRhID0gYXNzb3J0X3Jlc3VsdHNfZGYpCgpybShhc3NvcnRfcmVzdWx0c19saXN0LCBpbnB1dF9kZl9zdHVkeSwgaW5uZXJfcmVzdWx0X2xpc3QsIGlucHV0X2RmX21ldGhvZCwgaSwgaiwgZGZfbGlzdCkKCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KQppZihrZWVwX3RpbWUpIHRvYygpCgpgYGAKRXZlbiBpZiB0aGUgb2RkcyByYXRpb3MgYXJlIHZlcnkgaGlnaCBpbiBzb21lIGNhc2VzIChhY3R1YWxseSBJbmYsIGR1ZSB0byBkaXZpc2lvbiBieSAwKSB0aGV5IGFyZSBub3Qgc2lnbmlmaWNhbnQuIE15IGNvbmNsdXNpb24gaXMgdGhhdCB0aGVyZSBpcyBub3Qgc3VmZmljaWVudCBzdXBwb3J0IGZvciB0aGUgb2NjdXJyZW5jZSBvZiB0YXhvbm9taWMgYXNzb3J0YXRpdml0eSAoaS5lLiBmb3IgYSBzaWduaWZpY2FudGx5IGhpZ2hlciBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgYSBjb3ByZXNlbmNlIGxpbmsgYmV0d2VlbiBtZW1iZXJzIG9mIHRoZSBzYW1lIGZhbWlseSkuICAKU2ltaWxhciByZXN1bHRzIGNhbiBiZSBvYnRhaW5lZCB3aGVuIGxvb2tpbmcgYXQgdGhlIG9yZGVyIGFuZCBjbGFzcyBsZXZlbC4gIAoKCiMgQ2l0YXRpb25zIGFuZCBjb3B5cmlnaHQuICAKCiMjIENpdGF0aW9ucy4gIAoKQ2l0YXRpb25zIGZvciB0aGUgbWV0YXRheG9ub21pYyBzdHVkaWVzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhcmUgaW4gdGhlIHN0dWR5X21ldGFkYXRhIGRhdGEgZnJhbWUgKGlmIGFueSkuIFRoZSBtYWluIHBhY2thZ2UgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGlzIE5ldENvTWksIGFuZCBvZiBjb3Vyc2UsIFBoeWxvc2VxLiBDaXRhdGlvbnMgZm9yIGFsbCBwYWNrYWdlcyBhcmUgYmVsb3cuCgpgYGB7ciBjaXRhdGlvbnN9CgphbGxfcGFja2FnZXMgPC0gYygiYmFzZSIsIC5jcmFuX3BhY2thZ2VzLCAuYmlvY19wYWNrYWdlcywgLmdpdGh1Yl9wYWNrYWdlcykKbWFwKGFsbF9wYWNrYWdlcywgY2l0YXRpb24pCgojIGZvciByZXByb2R1Y2liaWxpdHkgeW91IHNob3VsZCBhbHNvIHJ1bgojIHNlc3Npb25JbmZvKCkKCgoKYGBgCgojIyBDb3B5cmlnaHQgbm90aWNlLiAgCgpTY3JpcHQgY3JlYXRlZCBieSBFdWdlbmlvIFBhcmVudGUgKGV1Z2VuaW8ucGFyZW50ZUB1bmliYXMuaXQpLCBVbml2ZXJzaXTDoCBkZWdsaSBTdHVkaSBkZWxsYSBCYXNpbGljYXRhLCAyMDIxCmh0dHBzOi8vZ2l0aHViLmNvbS9lcDE0MgpUaGlzIHNjcmlwdCBoYXMgYmVlbiB0ZXN0ZWQgd2l0aCBSIDQuMC41IGFuZCA0LjEgb24gdHdvIGRpZmZlcmVudCBBcHBsZSBjb21wdXRlcnMgd2l0aCA4IEdCIFJBTSBydW5uaW5nIE1hY09TIDEwLjE0LjYuICAKUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczogIApUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUuCg==